Vue3
1、Vue3 新特性介绍
- 重写双向数据绑定
- VDOM性能瓶颈
- Fragments
- Tree-Shaking的支持
- Composition API
1-1、重写双向绑定
1 | vue2 |
1-2、Vue3 优化Vdom
- 在Vue2中,每次更新diff,都是全量对比,Vue3则只对比带有标记的,这样大大减少了非动态内容的对比消耗
patch flag 优化静态树
1 | <span>Hello world!</span> |
- Vue3 编译后的 Vdom 是这个样子的
1 | export function render(_ctx,_cache,$props,$setup,$data,$options){return (_openBlock(),_createBlock(_Fragment,null,[ |
- 新增了 patch flag 标记
1
2
3
4
5
6
7
8
9
10
11
12
13TEXT = 1 // 动态文本节点
CLASS=1<<1,1 // 2//动态class
STYLE=1<<2,// 4 //动态style
PROPS=1<<3,// 8 //动态属性,但不包含类名和样式
FULLPR0PS=1<<4,// 16 //具有动态key属性,当key改变时,需要进行完整的diff比较。
HYDRATE_ EVENTS = 1 << 5,// 32 //带有监听事件的节点
STABLE FRAGMENT = 1 << 6, // 64 //一个不会改变子节点顺序的fragment
KEYED_ FRAGMENT = 1 << 7, // 128 //带有key属性的fragment 或部分子字节有key
UNKEYED FRAGMENT = 1<< 8, // 256 //子节点没有key 的fragment
NEED PATCH = 1 << 9, // 512 //一个节点只会进行非props比较
DYNAMIC_SLOTS = 1 << 10 // 1024 // 动态slot
HOISTED = -1 // 静态节点
BALL = -2
我们发现创建动态 dom 元素的时候,Vdom 除了模拟出来了它的基本信息之外,还给它加了一个标记: 1 /* TEXT */
这个标记就叫做 patch flag(补丁标记)
patch flag 的强大之处在于,当你的 diff 算法走到 _createBlock 函数的时候,会忽略所有的静态节点,只对有标记的动态节点进行对比,而且在多层的嵌套下依然有效。
尽管 JavaScript 做 Vdom 的对比已经非常的快,但是 patch flag 的出现还是让 Vue3 的 Vdom 的性能得到了很大的提升,尤其是在针对大组件的时候。
1-3、Vue3 Fragment
vue3 允许我们支持多个根节点
1
2
3
4<template>
<div>12</div>
<div>23</div>
</template>同时支持render JSX 写法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15render() {
return (
<>
{this.visable ? (
<div>{this.obj.name}</div>
) : (
<div>{this.obj.price}</div>
)}
<input v-model={this.val}></input>
{[1, 2, 3].map((v) => {
return <div>{v}-----</div>;
})}
</>
);
},同时新增了Suspense teleport 和 多 v-model 用法
1-4、Vue3 Tree shaking
简单来讲,就是在保持代码运行结果不变的前提下,去除无用的代码
在Vue2中,无论我们使用什么功能,它们最终都会出现在生产代码中。主要原因是Vue实例在项目中是单例的,捆绑程序无法检测到该对象的哪些属性在代码中被使用到
而Vue3源码引入tree shaking特性,将全局 API 进行分块。如果你不使用其某些功能,它们将不会包含在你的基础包中
就是比如你要用watch 就是import {watch} from ‘vue’ 其他的computed 没用到就不会给你打包减少体积
1-5、Vue 3 Composition Api
- Setup 语法糖式编程
例如 ref reactive watch computed toRefs toRaws 我们会在下面详解
2、构建项目
2-1、使用vite构建
- 命令
1
npm init vite@latest
2-2、使用vue脚手架构建
- 命令
1
npm init vue@latest
3、vite项目目录结构
public 下面的不会被编译 可以存放静态资源
assets 下面可以存放可编译的静态资源
components 下面用来存放我们的组件
App.vue 是全局组件
main ts 全局的ts文件
index.html 非常重要的入口文件 (webpack,rollup 他们的入口文件都是enrty input 是一个js文件 而Vite 的入口文件是一个html文件,他刚开始不会编译这些js文件 只有当你用到的时候 如script src=”xxxxx.js” 会发起一个请求被vite拦截这时候才会解析js文件)
3-1、SFC 语法规范
- .vue 件都由三种类型的顶层语法块所组成:template、script、style
1 | <template> |
- 其中的内容会被提取出来并传递给 @vue/compiler-dom,预编译为 JavaScript 的渲染函数,并附属到导出的组件上作为其 render 选项。
1 | <script> |
该脚本将作为 ES Module 来执行。
其默认导出的内容应该是 Vue 组件选项对象,它要么是一个普通的对象,要么是 defineComponent 的返回值。
1 | <script setup> |
1 | 该脚本会被预处理并作为组件的 setup() 函数使用,也就是说它会在每个组件实例中执行。<script setup> |
1 | <style> |
1 | <style> 标签可以通过 scoped 或 module attribute将样式封装在当前组件内。多个不同封装模式的 <style> 标签可以在同一个组件中混合使用 |
4、模板语法
4-1、第一种 vue2的写法(组合式API)
1 | <template> |
4-2、第二种 setup函数模式
- 定义的方法和属性必须要return出去
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19<template>
<div>
{{ age }}
</div>
</template>
<script>
export default {
setup(){
const age = 18
return {
age
}
}
}
</script>
<style scoped>
</style>
4-3、 第三种 setup语法糖
1 | <template> |
支持三元运算符
1
2
3
4
5
6
7
8
9
10
11
12
13<template>
<div>
<!-- 页面展示true -->
{{ age?"true":"false" }}
</div>
</template>
<script setup lang="ts">
const age:number = 1
</script>
<style scoped>
</style>支持API调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16<template>
<div>
<!-- 页面展示[{ "num": 1 },{ "num": 2 },{ "num": 3 },{ "num": 4 },{ "num": 5 }] -->
{{ age.map((item:any)=>{
return {num:item}
}) }}
</div>
</template>
<script setup lang="ts">
const age:number[] = [1,2,3,4,5]
</script>
<style scoped>
</style>
5、Vue指令用法
- v- 开头都是vue 的指令
v-text
- v-text 用来显示文本
1
2
3
4
5
6
7<template>
<div v-text="a"></div>
</template>
<script setup lang="ts">
const a:string = "我是一段文字xxxx"
</script>
v-html
- v-html 用来展示富文本 支持html标签 但不支持组件
1
2
3
4
5
6
7<template>
<div v-html="a"></div>
</template>
<script setup lang="ts">
</script>
v-if
- v-if 用来控制元素的显示隐藏(切换真假DOM)
1
2
3
4
5
6
7<template>
<div v-if="a">展示</div>
</template>
<script setup lang="ts">
const a:boolean = true
</script>
v-else-if
- v-else-if 表示 v-if 的“else if 块”。可以链式调用
1
2
3
4
5
6
7
8<template>
<div v-if="a==1">展示1</div>
<div v-else-if="a==2">展示2</div>
</template>
<script setup lang="ts">
const a:number = 2
</script>
v-else
- v-else v-if条件收尾语句
1
2
3
4
5
6
7
8
9
10<template>
<div v-if="a==1">展示1</div>
<div v-else-if="a==2">展示2</div>
<!-- 如果上面两个判断都没有通过 就展示v-else的内容 -->
<div v-else>展示3</div>
</template>
<script setup lang="ts">
const a:number = 10
</script>
v-show
- v-show 用来控制元素的显示隐藏(display none block Css切换)
1
2
3
4
5
6
7<template>
<div v-show="a==1">展示1</div>
</template>
<script setup lang="ts">
const a:number = 1
</script>
v-on
v-on 简写@ 用来给元素添加事件
1
2
3
4
5
6
7
8
9<template>
<div v-on:click="xxx">尽情点击我</div>
</template>
<script setup lang="ts">
const xxx=()=>{
console.log("点击了xxx")
}
</script>简写方式
1
2
3
4
5
6
7
8
9<template>
<div @click="xxx">尽情点击我</div>
</template>
<script setup lang="ts">
const xxx=()=>{
console.log("点击了xxx")
}
</script>动态切换方式
1
2
3
4
5
6
7
8
9
10<template>
<div @[event]="xxx">尽情点击我</div>
</template>
<script setup lang="ts">
const event='click'
const xxx=()=>{
console.log("点击了xxx")
}
</script>修饰符 .stop阻止事件冒泡
1 | <template> |
- 修饰符 .prevent阻止表单提交
1
2
3
4
5
6
7
8
9
10
11<template>
<form action="/">
<button @click.prevent="submit" type="submit">submit</button>
</form>
</template>
<script setup lang="ts">
const submit = () => {
console.log("child")
}
</script>
v-bind
- v-bind用来绑定元素的属性
1
2
3
4
5
6
7<template>
<div v-bind:id="id"></div>
</template>
<script setup lang="ts">
const id = "123"
</script>
- 简写方式
1
2
3
4
5
6
7<template>
<div :style="style">111111</div>
</template>
<script setup lang="ts">
const style = "border: 1px solid #ccc; color: red;"
</script>
v-model
- v-model 双向绑定
1
2
3
4
5
6
7
8
9
10
11
12<template>
<div>
<input type="text" v-model="a">
<div>{{ a }}</div>
</div>
</template>
<script setup lang="ts">
// ref 后面会讲到
import {ref} from "vue"
const a = ref("赵四眼")
</script>
v-for
- v-for 用来遍历元素
1
2
3
4
5
6
7
8
9
10<template>
<!-- item表示每一个元素 index表示索引 -->
<div v-for="(item,index) in arr" :key="index">
{{ item }}
</div>
</template>
<script setup lang="ts">
const arr:string[] = ['赵四眼','赵小眼','赵大眼']
</script>
v-once
- v-once性能优化只渲染一次
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16<template>
<!-- 标签上加了v-once之后 变量只会渲染一次 点击事件改变值 也不会重新渲染 -->
<div v-once>
{{ a }}
</div>
<button @click="xxx">点击</button>
</template>
<script setup lang="ts">
import {ref} from "vue"
let a=ref("赵四眼")
const xxx=()=>{
a.value='赵小眼'
}
</script>
v-memo
- 当组件重新渲染的时候,如果arr维持不变,那么对这个以及它的所有子节点的更新都将被跳过。事实上,即使是虚拟DOM的VNode创建也将被跳过,因为子树的记忆副本可以被重用。
1
2
3
4
5
6
7
8
9
10
11<template>
<!-- -->
<div v-for="(item,index) in arr" :key="index" v-memo=[item]>
{{ item }}
</div>
</template>
<script setup lang="ts">
let arr=[1,2,3,4,5,6,7,8,9,10,11]
</script>6、Vue核心虚拟Dom和 diff 算法
- 为什么要学习源码
1.可以提升自己学习更优秀的API设计和代码逻辑
2.面试的时候也会经常问源码相关的东西
3.更快的掌握vue和遇到问题可以定位
6-1、介绍虚拟DOM
- 虚拟DOM就是通过JS来生成一个AST节点树
- AST抽象语法树
- TS转JS的时候会使用AST
为什么要有虚拟DOM?
我们可以通过下面的例子
1
2
3
4
5
6let div = document.createElement('div')
let str = ''
for (const key in div) {
str += key + ' '
}
console.log(str)发现一个dom上面的属性是非常多的
1
align title lang translate dir hidden accessKey draggable spellcheck autocapitalize contentEditable enterKeyHint isContentEditable inputMode virtualKeyboardPolicy offsetParent offsetTop offsetLeft offsetWidth offsetHeight innerText outerText onbeforexrselect onabort onbeforeinput onblur oncancel oncanplay oncanplaythrough onchange onclick onclose oncontextlost oncontextmenu oncontextrestored oncuechange ondblclick ondrag ondragend ondragenter ondragleave ondragover ondragstart ondrop ondurationchange onemptied onended onerror onfocus onformdata oninput oninvalid onkeydown onkeypress onkeyup onload onloadeddata onloadedmetadata onloadstart onmousedown onmouseenter onmouseleave onmousemove onmouseout onmouseover onmouseup onmousewheel onpause onplay onplaying onprogress onratechange onreset onresize onscroll onsecuritypolicyviolation onseeked onseeking onselect onslotchange onstalled onsubmit onsuspend ontimeupdate ontoggle onvolumechange onwaiting onwebkitanimationend onwebkitanimationiteration onwebkitanimationstart onwebkittransitionend onwheel onauxclick ongotpointercapture onlostpointercapture onpointerdown onpointermove onpointerrawupdate onpointerup onpointercancel onpointerover onpointerout onpointerenter onpointerleave onselectstart onselectionchange onanimationend onanimationiteration onanimationstart ontransitionrun ontransitionstart ontransitionend ontransitioncancel oncopy oncut onpaste dataset nonce autofocus tabIndex style attributeStyleMap attachInternals blur click focus inert oncontentvisibilityautostatechange onscrollend popover onbeforetoggle onbeforematch hidePopover showPopover togglePopover namespaceURI prefix localName tagName id className classList slot attributes shadowRoot part assignedSlot innerHTML outerHTML scrollTop scrollLeft scrollWidth scrollHeight clientTop clientLeft clientWidth clientHeight onbeforecopy onbeforecut onbeforepaste onsearch elementTiming onfullscreenchange onfullscreenerror onwebkitfullscreenchange onwebkitfullscreenerror role ariaAtomic ariaAutoComplete ariaBusy ariaBrailleLabel ariaBrailleRoleDescription ariaChecked ariaColCount ariaColIndex ariaColSpan ariaCurrent ariaDescription ariaDisabled ariaExpanded ariaHasPopup ariaHidden ariaInvalid ariaKeyShortcuts ariaLabel ariaLevel ariaLive ariaModal ariaMultiLine ariaMultiSelectable ariaOrientation ariaPlaceholder ariaPosInSet ariaPressed ariaReadOnly ariaRelevant ariaRequired ariaRoleDescription ariaRowCount ariaRowIndex ariaRowSpan ariaSelected ariaSetSize ariaSort ariaValueMax ariaValueMin ariaValueNow ariaValueText children firstElementChild lastElementChild childElementCount previousElementSibling nextElementSibling after animate append attachShadow before closest computedStyleMap getAttribute getAttributeNS getAttributeNames getAttributeNode getAttributeNodeNS getBoundingClientRect getClientRects getElementsByClassName getElementsByTagName getElementsByTagNameNS getInnerHTML hasAttribute hasAttributeNS hasAttributes hasPointerCapture insertAdjacentElement insertAdjacentHTML insertAdjacentText matches prepend querySelector querySelectorAll releasePointerCapture remove removeAttribute removeAttributeNS removeAttributeNode replaceChildren replaceWith requestFullscreen requestPointerLock scroll scrollBy scrollIntoView scrollIntoViewIfNeeded scrollTo setAttribute setAttributeNS setAttributeNode setAttributeNodeNS setPointerCapture toggleAttribute webkitMatchesSelector webkitRequestFullScreen webkitRequestFullscreen checkVisibility getAnimations setHTML nodeType nodeName baseURI isConnected ownerDocument parentNode parentElement childNodes firstChild lastChild previousSibling nextSibling nodeValue textContent ELEMENT_NODE ATTRIBUTE_NODE TEXT_NODE CDATA_SECTION_NODE ENTITY_REFERENCE_NODE ENTITY_NODE PROCESSING_INSTRUCTION_NODE COMMENT_NODE DOCUMENT_NODE DOCUMENT_TYPE_NODE DOCUMENT_FRAGMENT_NODE NOTATION_NODE DOCUMENT_POSITION_DISCONNECTED DOCUMENT_POSITION_PRECEDING DOCUMENT_POSITION_FOLLOWING DOCUMENT_POSITION_CONTAINS DOCUMENT_POSITION_CONTAINED_BY DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC appendChild cloneNode compareDocumentPosition contains getRootNode hasChildNodes insertBefore isDefaultNamespace isEqualNode isSameNode lookupNamespaceURI lookupPrefix normalize removeChild replaceChild addEventListener dispatchEvent removeEventListener
所以直接操作DOM非常浪费性能
解决方案就是 我们可以用JS的计算性能来换取操作DOM所消耗的性能,既然我们逃不掉操作DOM这道坎,但是我们可以尽可能少的操作DOM
操作JS是非常快的
6-2、介绍Diff算法
1
2
3
4
5
6
7
8
9
10<template>
<div>
<div :key="item" v-for="(item) in Arr">{{ item }}</div>
</div>
</template>
<script setup lang="ts">
const Arr: Array<string> = ['A', 'B', 'C', 'D']
Arr.splice(2,0,'DDD')
</script>7、ref全家桶
7-1、ref(深层次的响应式)
- 接受一个内部值并返回一个响应式且可变的ref对象。ref对象仅有一个.value property,指向该内部值。
不使用ref
1
2
3
4
5
6
7
8
9
10
11
12<template>
<div>{{ mas.name }}</div>
<button @click="xxx">修改</button>
</template>
<script setup lang='ts'>
const mas = { name: "张三" }
const xxx =()=>{
mas.name = "李四"
console.log(mas)
}
</script>- 结果如下
我们这样操作是无法改变mas在页面上的值 因为mas不是响应式的无法被vue跟踪
ref用法
修改值的时候 必须加上.value 这是固定语法 渲染的时候不需要加
1
2
3
4
5
6
7
8
9
10
11
12
13<template>
<div>{{ mas.name }}</div>
<button @click="xxx">修改</button>
</template>
<script setup lang='ts'>
import {ref} from "vue"
const mas = ref({ name: "张三" })
const xxx =()=>{
mas.value.name = "李四"
console.log(mas)
}
</script>结果如下
7-2、isRef
- 判断是不是一个ref对象
1
2
3
4
5
6
7import { ref,isRef } from 'vue'
let message = ref("我是message")
let notRef = 123
const changeMsg = () => {
console.log(isRef(message)); //true
console.log(isRef(notRef)); //false
}
7-3、shallowRef(浅层次的响应式)
创建一个跟踪自身 .value变化的ref,但不会使其值也变成响应式的
1
2
3
4
5
6
7
8
9
10
11
12<template>
<hr>
<div>{{ mas1 }}</div>
<button @click="xxx">修改</button>
</template>
<script setup lang='ts'>
import {shallowRef} from "vue"
const mas1 = shallowRef({ name: "张三" })
const xxx =()=>{
mas1.value.name = "王五"
}- 我们点击修改按钮的时候 并没有发生修改 原因是shallowRef是浅层响应式 只能跟踪.value 不能跟踪.value中的属性
正确写法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<template>
<hr>
<div>{{ mas1 }}</div>
<button @click="xxx">修改</button>
</template>
<script setup lang='ts'>
import {shallowRef} from "vue"
const mas1 = shallowRef({ name: "张三" })
const xxx =()=>{
mas1.value={
name:"王五"
}
}
</script>注意
- ref和shallowRef是不能一块写的 不然会影响shallowRef 造成视图的更新
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16<template>
<div>Ref:{{ mas }}</div>
<hr>
<div>shallowRef:{{ mas1 }}</div>
<button @click="xxx">修改</button>
</template>
<script setup lang='ts'>
import {ref,shallowRef} from "vue"
const mas = ref({ name: "张三" })
const mas1 = shallowRef({ name: "张三" })
const xxx =()=>{
mas.value.name = "李四"
mas1.value.name = "王五"
}
</script> - 当我们点击修改按钮的时候 即便shallowRef是浅层响应式 也会被ref影响到
7-4、triggerRef
- 强制更新页面DOM
1
2
3
4
5
6
7
8
9
10
11
12
13
14<template>
<hr>
<div>shallowRef:{{ mas1 }}</div>
<button @click="xxx">修改</button>
</template>
<script setup lang='ts'>
import {shallowRef,triggerRef} from "vue"
const mas1 = shallowRef({ name: "张三" })
const xxx =()=>{
mas1.value.name = "王五" // 本来shallowRef这个值是不会被修改的
triggerRef(mas1) // 但是我们加上triggerRef 他会帮我们强制更新
}
</script>
7-5、customRef
自定义ref 类似ref的源码
customRef 是个工厂函数要求我们返回一个对象 并且实现 get 和 set 适合去做防抖之类的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30<template>
<hr>
<div>{{ obj }}</div>
<button @click="xxx">修改</button>
</template>
<script setup lang='ts'>
import {customRef} from "vue"
function myRef<T>(value:T){
return customRef((track,trigger)=>{
return {
get(){ // 收集依赖
track()
return value
},
set(newVal){ // 修改
value = newVal
trigger() // 强制更新
}
}
})
}
const obj=myRef<string>('张三')
const xxx =()=>{
obj.value="李四"
}
</script>customRef结合防抖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33<template>
<hr>
<div>{{ obj }}</div>
<button @click="xxx">修改</button>
</template>
<script setup lang='ts'>
import { customRef } from "vue"
let time:any
function myRef<T>(value: T) {
return customRef((track, trigger) => {
return {
get() { // 收集依赖
track()
return value
},
set(newVal) { // 修改
clearTimeout(time)
time = setTimeout(() => {
console.log("触发了")
value = newVal
trigger() // 强制更新
time=null
},500)
}
}
})
}
const obj = myRef<string>('张三')
const xxx = () => {
obj.value = "李四"
}
</script>
7-6、ref小知识
我们log输出以下
1
2
3import { ref } from "vue"
const mas = ref<string>("string")
console.log(mas)这个格式 查看起来很不方便
Vue 已经想到 了 帮我们做了格式化
7-7、ref获取dom元素
- 你获取元素声明的变量名要和元素上ref的属性名一样
1
2
3
4
5
6
7
8
9
10
11
12
13<template>
<hr>
<div ref="dom">我是张三</div>
<button @click="xxx">点击</button>
</template>
<script setup lang='ts'>
import { ref } from "vue"
const dom = ref<HTMLDivElement>()
const xxx=()=>{
console.log(dom.value) // <div>我是张三</div>
}
</script>
8、reactive全家桶
8-1、reactive
- reactive 是不可以绑定普通的数据类型这样是不允许 会给我们报错
- 绑定普通的数据类型 我们可以 使用ref
- 你如果用ref去绑定对象或者数组等复杂的数据类型 其实也是去调用reactive
- ref支持所有的类型 reactive支持引用类型 Array Object Map Set
- ref 取值、赋值 都需要加.value reactive不需要.value
对象写法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24<template>
<form>
<input type="text" v-model="form.name">
<br>
<input type="text" v-model="form.age">
<br>
<button @click.prevent="xxx">提交</button>
</form>
</template>
<script setup lang='ts'>
import { reactive } from "vue"
type M = {
name: string,
age: number,
}
const form = reactive<M>({
name:"zs",
age:18
})
const xxx=()=>{
console.log(form)
}
</script>数组写法
使用reactive需要注意的问题
数组异步赋值问题
这样赋值页面是不会变化的 因为会脱离响应式
1
2
3
4
5let arr = reactive<string[]>([])
const xxx = () => {
let res = ['WBG', 'IG', 'TES']
arr = res // reactive不可以直接赋值
}reactive是proxy类型的 不能直接赋值 否则会破坏响应式对象的
解决方案1 数组 push加解构
1
2
3
4
5let arr = reactive<string[]>([])
const xxx = () => {
let res = ['WBG', 'IG', 'TES']
arr.push(...res)
}解决方案2 包裹一层对象
1
2
3
4
5
6
7
8let person = reactive({
list:[]
})
setTimeout(() => {
const arr = [1, 2, 3]
person.list = arr;
console.log(person);
},1000)
8-2、readonly
- 只读
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16<template>
<button @click="xxx">查看</button>
</template>
<script setup lang='ts'>
import { reactive,readonly} from "vue"
let obj = reactive({
name:"张三"
})
let read = readonly(obj)
read.name = "李四" // 报错 无法为“name”赋值,因为它是只读属性
const xxx = () => {
console.log(obj) // name: "张三"
console.log(read) // name: "张三"
}
</script>
8-3、shallowReactive
- 只能对浅层的数据 如果是深层的数据只会改变值 不会改变视图
- 和shallowRef一样 也会被reactive影响
1
2
3
4
5
6
7
8
9
10
11
12
13import { shallowReactive } from "vue"
let obj = shallowReactive({
foo:{
bar:{
num:1
}
}
})
const xxx = () => {
obj.foo.bar.num = 2
console.log(obj) // 值改变了 但是视图没变
}
9、to系列全家桶(toRef、toRefs、toRaw)
9-1、toRef
- 如果原始对象是非响应式的就不会更新视图 数据是会变的
- toRef只能修改响应式对象的值 非响应式视图毫无变化
1
2
3
4
5
6
7import { reactive, toRef } from "vue"
const man = reactive({ name: "赵四眼", age: 20 })
const name = toRef(man,"name")
const xxx=()=>{
name.value = "zzm" // toRef只能修改响应式对象的值
console.log(man)
} - 应用场景
- 比如一个函数需要一个参数 而那个参数还是reactive中的值 我们就可以使用toRef
1
2
3
4
5
6
7const man = reactive({ name: "赵四眼", age: 20 })
function useMemo(aaa:any){
console.log(aaa)
}
useMemo(toRef(man,"name"))
- 比如一个函数需要一个参数 而那个参数还是reactive中的值 我们就可以使用toRef
9-2、toRefs
可以帮我们批量创建ref对象主要是方便我们解构使用
1
2
3
4
5
6
7
8
9
10
11import { reactive, toRefs } from 'vue'
const obj = reactive({
foo: 1,
bar: 1
})
let { foo, bar } = toRefs(obj)
// 点击事件
const xxx=()=>{
foo.value = 10 // 视图更新
}
console.log(foo, bar); // 解构出来的值 也是响应式对象不可以直接解构 直接解构出的值 是没有响应式的
1
2
3
4
5
6
7
8
9
10
11import { reactive } from 'vue'
const obj = reactive({
foo: 1,
bar: 1
})
let { foo, bar } = obj
// 点击事件
const xxx=()=>{
foo = 10 // 视图不会更新
}
console.log(foo, bar); // 解构出来的值 是普通属性- 分析源码
1
2
3
4
5
6
7
8const toRefs=<T extends object>(object:T){
const map:any = {}
for(let key in object){
// 对你传入的对象中每一个属性执行了一遍toRef
map[key] = toRef(object,key)
}
return map
}9-3、toRaw
- 响应式对象转普通对象
1
2
3
4
5
6
7
8
9import { reactive,toRaw } from 'vue'
const man = reactive({
name:"zzm",
age:18,
like:"蛋仔"
})
let obj = toRaw(man)
console.log(obj); // {name: 'zzm', age: 18, like: '蛋仔'}
10、认识computed计算属性
10-1、computed用法
- 计算属性就是当依赖的属性的值发生变化的时候,才会触发他的更改,如果依赖的值,不发生变化的时候,使用的是缓存中的属性值。
选项式写法 支持一个对象传入get函数以及set函数自定义操作 可以修改值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34<template>
<div>
<div>
姓:<input type="text" v-model="firstName">
</div>
<div>
名:<input type="text" v-model="lastName">
</div>
<div>
全名:{{ name }}
</div>
<button @click="xxx">修改</button>
</div>
</template>
<script setup lang='ts'>
import { computed, ref } from "vue"
let firstName = ref("张")
let lastName = ref("三")
let name = computed<string>({
get(){
return firstName.value+""+lastName.value
},
set(newVal){ // set方法能接收到name改变后的值
console.log(newVal); // 赵-四眼
[firstName.value, lastName.value]=newVal.split('-')
}
})
// 点击修改姓名事件
const xxx=()=>{
name.value="赵-四眼"
}
</script>函数式写法 只能支持一个getter函数 不允许修改值的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24<template>
<div>
<div>
姓:<input type="text" v-model="firstName">
</div>
<div>
名:<input type="text" v-model="lastName">
</div>
<div>
全名:{{ name }}
</div>
</div>
</template>
<script setup lang='ts'>
import { computed, ref } from "vue"
let firstName = ref("张")
let lastName = ref("三")
// 2、函数式写法 只读的
let name=computed(()=>{
return firstName.value+""+lastName.value
})
</script>购物车小案例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86<template>
<div>
<div>
<input v-model="keyWord" type="text" placeholder="搜索">
</div>
<div style="margin-top: 20px;">
<table>
<thead>
<tr>
<th>物品名称</th>
<th>物品单价</th>
<th>物品数量</th>
<th>物品总价</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in searchData">
<td>{{ item.name }}</td>
<td>{{ item.price }}</td>
<td>
<!-- 如果item.num大于1 每点击一次就-1 否则什么都不做 -->
<button @click="item.num > 1 ? item.num-- : null">-</button>
{{ item.num }}
<!-- 每点击一次就+1 -->
<button @click="item.num++">+</button>
</td>
<td>{{ item.num * item.price }}</td>
<td @click="del(index)">删除</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="5" align="right">
总价:{{ total }}
</td>
</tr>
</tfoot>
</table>
</div>
</div>
</template>
<script setup lang='ts'>
import { ref, computed, reactive } from "vue"
interface Data {
name: string,
price: number,
num: number
}
let keyWord = ref<string>("")
let data = reactive<Data[]>([
{
name: "zzm的绿帽子",
price: 999,
num: 10
},
{
name: "zzm的破裤头",
price: 90,
num: 2
},
{
name: "zzm本人",
price: 10000,
num: 1
}
])
// 删除
const del = (index: number) => {
data.splice(index, 1)
}
// 总价
const total = computed(() => {
return data.reduce((pre: number, next: Data) => {
return pre + next.num * next.price
}, 0)
})
// 搜索
const searchData = computed(() => {
return data.filter((item: Data) => {
return item.name.includes(keyWord.value)
})
})
</script>11、认识watch监听器
- watch 需要侦听特定的数据源,并在单独的回调函数中执行副作用
- watch第一个参数监听源
- watch第二个参数回调函数cb(newVal,oldVal)
- watch第三个参数一个options配置项是一个对象{
immediate:true //是否立即调用一次
deep:true //是否开启深度监听
}
监听Ref 案例
1
2
3
4
5
6import { watch, ref } from "vue"
let message = ref<string>("zzm")
watch(message, (newVal, oldVal) => {
console.log(newVal, oldVal)
})监听多个Ref案例 注意变成数组啦
1
2
3
4
5
6
7import { watch, ref } from "vue"
let message1 = ref<string>("zzm")
let message2 = ref<string>("赵四眼")
watch([message1,message2], (newVal, oldVal) => {
console.log("新的值--",newVal) // 新的值-- (2) ['zzm1', '赵四眼']
console.log("旧的值--",oldVal) // 旧的值-- (2) ['zzm', '赵四眼']
})监听复杂类型Ref案例 需要开启深度监听
- deep:true 深度监听
1
2
3
4
5
6
7
8
9
10
11
12
13
14import { watch, ref } from "vue"
let message = ref({
foo:{
bar:{
name:"zzm"
}
}
})
watch(message, (newVal, oldVal) => {
console.log("新的值--",newVal)
console.log("旧的值--",oldVal)
},{
deep: true, // 深度监听
})
监听reactive
- 使用reactive监听深层对象开启和不开启deep 效果一样
- 不开启深度监听 也是可以监听到的
1
2
3
4
5
6
7
8
9
10
11
12
13import { ref, watch ,reactive} from 'vue'
let message = reactive({
nav:{
bar:{
name:""
}
}
})
watch(message, (newVal, oldVal) => {
console.log('新的值----', newVal);
console.log('旧的值----', oldVal);
})
监听reactive 单一值
- 如果要监听单一值 我们需要把他变成一个函数
1
2
3
4
5
6
7
8
9
10
11
12
13import {watch ,reactive} from 'vue'
let message = reactive({
foo:{
bar:{
name:"zzm"
}
}
})
// 将这个单一值变为函数
watch(()=>message.foo.bar.name, (newVal, oldVal) => {
console.log('新的值----', newVal);
console.log('旧的值----', oldVal);
})
首次执行一次
- watch数据没发生改变是不会执行的 我们可以开启immediate:true 让它立即执行一次
1
2
3
4
5
6
7
8import { watch, ref } from "vue"
let message = ref<string>("zzm")
watch(message, (newVal, oldVal) => {
console.log(newVal, oldVal)
},{
immediate:true // 立即调用一次
})
wacth的options配置项的方法
1
2
3
4
5
6
7watch(监听的数据, (新的值, 旧的值) => {
console.log(新的值, 旧的值)
},{
deep:true // 深度监听
immediate:true // 立即调用一次
flush:"pre" // pre 组件更新之前调用 sync 同步执行 post 组件更新之后执行
})12、认识watchEffect高级侦听器
- 立即执行传入的一个函数,同时响应式追踪其依赖,并在其依赖变更时重新运行该函数。
基本用法
- 如果用到message 就只会监听message 就是用到几个监听几个 而且是非惰性 会默认调用一次
1
2
3
4
5
6
7import {ref, watchEffect} from "vue"
let message = ref<string>("zzm")
// 当massage改变时 就会被watchEffect监听到
// 默认会执行一次
watchEffect(()=>{
console.log("message",message.value)
})
清除副作用
- 就是在触发监听之前会调用一个函数可以处理你的逻辑例如防抖
1
2
3
4
5
6
7
8
9
10import {ref, watchEffect} from "vue"
let message = ref<string>("zzm")
watchEffect((oninvalidate)=>{
console.log("message",message.value)
// 清除副作用
oninvalidate(()=>{
})
})
停止监听
- 停止跟踪 watchEffect 返回一个函数 调用之后将停止更新
1
2
3
4
5
6
7import {ref, watchEffect} from "vue"
let message = ref<string>("zzm")
const stop = watchEffect(()=>{
console.log("message",message.value)
})
stop() //停止监听
配置项
1
2
3
4
5
6
7
8
9
10
11
12
13
14<template>
<div id="dom"></div>
</template>
<script setup lang='ts'>
import {watchEffect} from "vue"
watchEffect(()=>{
const dom = document.querySelector("#dom")
console.log(dom) // <div id="dom"></div>
},{
flush:"post" // pre 组件更新之前调用(就是组件初始化dom完成之前执行) sync 同步执行 post 组件更新之后执行(就是组件初始化dom完成之后执行)
})
</script>onTrigger 可以帮助我们调试 watchEffect
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15import { watchEffect, ref } from 'vue'
let message = ref<string>('')
let message2 = ref<string>('')
watchEffect((oninvalidate) => {
//console.log('message', message.value);
oninvalidate(()=>{
})
console.log('message2', message2.value);
},{
flush:"post",
onTrigger () {
debugger
}
})13、认识组件和生命周期
13-1、组件基础
每一个.vue 文件呢都可以充当组件来使用
每一个组件都可以复用
先创建一个.vue文件
- 父组件使用
- 页面效果
13-2、生命周期
- onBeforeMount()
在组件DOM实际渲染安装之前调用。在这一步中,根元素还不存在。 - onMounted()
在组件的第一次渲染后调用,该元素现在可用,允许直接DOM访问 - onBeforeUpdate()
数据更新时调用,发生在虚拟 DOM 打补丁之前。 - onUpdated()
DOM更新后,updated的方法即会调用。 - onBeforeUnmount()
在卸载组件实例之前调用。在这个阶段,实例仍然是完全正常的。 - onUnmounted()
卸载组件实例后调用。调用此钩子时,组件实例的所有指令都被解除绑定,所有事件侦听器都被移除,所有子组件实例被卸载。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29import {onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmounted} from "vue"
// beforeCreate created setup语法糖模式是没有这两个生命周期的 setup去替代
// 首先执行setup
console.log("setup")
// 创建的钩子
onBeforeMount(()=>{
console.log("创建之前========>")
})
onMounted(()=>{
console.log("创建完成========>")
})
// 更新的钩子
onBeforeUpdate(()=>{
console.log("更新之前========>")
})
onUpdated(()=>{
console.log("更新完成========>")
})
// 销毁的钩子
onBeforeUnmount(()=>{
console.log("销毁之前========>")
})
onUnmounted(()=>{
console.log("销毁完成========>")
})页面加载完毕
获取dom
- onBeforeMount 读不到dom onMounted 可以读取dom
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18<template>
<h1>我是子组件</h1>
<div ref="dom"></div>
</template>
<script setup lang="ts">
import {ref,onBeforeMount,onMounted} from "vue"
const dom = ref<HTMLDivElement>()
// 创建的钩子
onBeforeMount(()=>{
console.log("创建之前========>",dom) // 创建之前========> undefined
})
onMounted(()=>{
console.log("创建完成========>",dom) // 创建完成========> <div>
})
</script>
触发更新的钩子
- onBeforeUpdate 获取更新之前的dom onUpdated 获取更新之后的dom
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24<template>
<h1>我是子组件</h1>
<div ref="dom">{{ str }}</div>
<button @click="xxx">修改</button>
</template>
<script setup lang="ts">
import {ref,onBeforeUpdate,onUpdated} from "vue"
const str = ref<string>("zzm")
const dom = ref<HTMLDivElement>()
const xxx=()=>{
str.value = "111"
}
// 更新的钩子
onBeforeUpdate(()=>{
console.log("更新之前========>",dom.value?.innerText) // 更新之前========> zzm
})
onUpdated(()=>{
console.log("更新完成========>",dom.value?.innerText) // 更新完成========> 111
})
</script>
触发销毁钩子
- 我们对子组件进行销毁和重建 就会触发子组件中的销毁钩子
1
2
3
4
5
6
7
8
9
10
11
12
13<template>
<HelloWorld v-if="flag"/>
<button @click="xxx">销毁 重建</button>
</template>
<script setup lang='ts'>
import HelloWorld from "./components/HelloWorld.vue"
import {ref} from "vue"
const flag = ref<boolean>(true)
const xxx = () =>{
flag.value = !flag.value
}
</script>
其他
import {onRenderTriggered,onRenderTracked} from “vue”
1
2
3
4
5
6
7
8// 获取依赖
onRenderTracked((e)=>{
console.log(e)
})
// 获取修改后的依赖
onRenderTriggered((e)=>{
console.log(e)
})14、父子组件传参
14-1、父传子
父组件通过v-bind绑定一个数据,然后子组件通过defineProps接受传过来的值,
如以下代码
给子组件Son传递了一个title字符串类型和arr数组类型
1
2
3
4
5
6
7
8
9<template>
<h1>父级</h1>
<son :title="tit" :arr="[1,2,3]"/>
</template>
<script setup lang='ts'>
import Son from './components/Son.vue';
let tit = "zzm"
</script>子组件接收
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39<template>
<h2>子级</h2>
<!-- 在模板中可以直接使用title属性 -->
<div>接收的值:{{ title }}</div>
</template>
<script setup lang='ts'>
// 接收父组件传来的值
// JS方式接收
const props=defineProps({
title:{
type:String,
default:"默认值",
},
arr:{
type:Array,
default:null
}
})
// console.log(title) 不能直接读取
console.log(props.title) // zzm
// TS方式接收
const props=defineProps<{
title:string,
arr:number[]
}>()
console.log(props)
// TS设置默认值 TS特有的
withDefaults(defineProps<{
title:string,
arr:number[]
}>(),{
arr:()=>[1,2,3,4]
})
</script>14-2、子传父
- 是通过defineEmits派发一个事件
1
2
3
4
5
6
7
8
9
10
11
12
13<template>
<h2>子级</h2>
<button @click="send">给父组件传值</button>
</template>
<script setup lang='ts'>
const emit = defineEmits(['zzm'])
const send = () =>{
// emit("事件名称","数据")
emit('zzm','赵四眼')
}
</script> - 我们在子组件绑定了一个click 事件 然后通过defineEmits 注册了一个自定义事件
- 点击click 触发 emit 去调用我们注册的事件 然后传递参数
- 父组件接受子组件的事件
1
2
3
4
5
6
7
8
9
10
11<template>
<h1>父级</h1>
<son @zzm="getName"/>
</template>
<script setup lang='ts'>
import Son from './components/Son.vue';
const getName=(e:string)=>{
console.log(e) // 赵四眼
}
</script>
TS写法
子组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<template>
<h2>子级</h2>
<button @click="send">给父组件传值</button>
</template>
<script setup lang='ts'>
const emit = defineEmits<{
(e:"zzm",name:string):void
}>()
const send = () =>{
// emit("事件名称","数据")
emit('zzm','赵四眼')
}
</script>父组件
1
2
3
4
5
6
7
8
9
10
11<template>
<h1>父级</h1>
<son @zzm="getName"/>
</template>
<script setup lang='ts'>
import Son from './components/Son.vue';
const getName=(e:string)=>{
console.log(e)
}
</script>
子组件暴露给父组件内部属性
- 子组件通过defineExpose
1
2
3
4
5
6
7
8
9
10
11<template>
<h2>子级</h2>
</template>
<script setup lang='ts'>
defineExpose({
name:"赵四眼",
open:()=> console.log(111) // 可以传递方法
})
</script> - 我们父组件获取子组件实例通过ref
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16<template>
<h1>父级</h1>
<son ref="zzm"/>
</template>
<script setup lang='ts'>
import Son from './components/Son.vue';
import {ref,onMounted} from "vue"
// 名称必须要和子组件的ref属性名一致
let zzm = ref<InstanceType<typeof Son>>();
onMounted(()=>{
console.log(zzm.value?.name) // 赵四眼
zzm.value?.open() // 调用 打印111
})
</script>
15、全局组件,局部组件,递归组件
15-1、全局组件
- 例如组件使用频率非常高(table,Input,button,等)这些组件 几乎每个页面都在使用便可以封装成全局组件
- 配置全局组件
- 在main.ts 引入我们的组件跟随在createApp(App) 后面 切记不能放到mount 后面这是一个链式调用用
- 其次调用 component 第一个参数组件名称 第二个参数组件实例
- mian.ts
1
2
3
4
5import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import Card from './components/Card.vue'
createApp(App).component('Card',Card).mount('#app') - 使用方法
- 直接在其他vue页面 立即使用即可 无需引入
1
2
3
4<template>
<h1>父级</h1>
<Card/>
</template>
15-2、局部组件
- 就是在一个组件内(A) 通过import 去引入别的组件(B) 称之为局部组件
- 应为B组件只能在A组件内使用 所以是局部组件
- 如果C组件想用B组件 就需要C组件也手动import 引入 B 组件
1
2
3
4
5
6
7
8<template>
<h1>父级</h1>
<Card/>
</template>
<script setup lang='ts'>
import Card from './components/Card.vue';
</script>
15-3、递归组件
在父组件配置数据结构 数组对象格式 传给子组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47<template>
<h1>父级</h1>
<Tree :data="data"/>
</template>
<script setup lang='ts'>
import { reactive } from 'vue'
import Tree from "./components/Tree.vue"
interface Tree {
name: string;
checked: boolean;
children?: Tree[];
}
// 数据
const data = reactive<Tree[]>([
{
name: "1",
checked: true,
children: [
{
name: "1-1",
checked: true,
}
]
},
{
name: "2",
checked: true,
},
{
name: "3",
checked: true,
children: [
{
name: "3-1",
checked: true,
children: [
{
name: "3-1-1",
checked: true,
}
]
}
]
}
])
</script>子组件接收值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21<template>
<div v-for="item in data">
<input type="checkbox" v-model="item.checked">
<span>{{ item.name }}</span>
<!-- Tree在自身组件中调用自身 -->
<Tree v-if="item?.children?.length" :data="item?.children"/>
</div>
</template>
<script setup lang='ts'>
// TS定义类型
interface Tree {
name: string;
checked: boolean;
children?: Tree[];
}
// 接收父组件传来的数据
defineProps<{
data?:Tree[]
}>()
</script>
16、动态组件
什么是动态组件 就是:让多个组件使用同一个挂载点,并动态切换,这就是动态组件。
在挂载点使用component标签,然后使用v-bind:is=”组件”
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53<template>
<div style="display: flex;">
<div @click="switchCom(item,index)" :class="[active==index?'active':'']" class="tabs" v-for="(item,index) in data">
<div>{{ item.name }}</div>
</div>
</div>
<!-- component标签中的:is 属性 用来动态展示组件 -->
<component :is="comId"></component>
</template>
<script setup lang='ts'>
import { reactive, ref } from "vue"
import A from './components/A.vue';
import B from './components/B.vue';
import C from './components/C.vue';
const active = ref(0) // 默认为0
const comId = ref(A) // 默认为A标签
const data = reactive([
{
name: "A组件",
com: A
},
{
name: "B组件",
com: B
},
{
name: "C组件",
com: C
}
])
// 点击切换事件
const switchCom=(item:any,index:number)=>{
active.value = index
comId.value = item.com
}
</script>
<style scoped lang="scss">
.tabs{
padding: 5px 10px;
margin: 5px;
border: 1px solid #ccc;
cursor: pointer;
}
.active{
background-color: aqua;
}
</style>注意事项
1.在Vue2 的时候is 是通过组件名称切换的 在Vue3 setup 是通过组件实例切换的
2.如果你把组件实例放到Reactive Vue会给你一个警告runtime-core.esm-bundler.js:38 [Vue warn]: Vue received a Component which was made a reactive object. This can lead to unnecessary performance overhead, and should be avoided by marking the component with
markRawor usingshallowRefinstead ofref.
Component that was made reactive:这是因为reactive 会进行proxy 代理 而我们组件代理之后毫无用处 节省性能开销 推荐我们使用shallowRef 或者 markRaw 跳过proxy 代理
修改如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16const comId = shallowRef(A) // 默认为A标签
const data = reactive([
{
name: "A组件",
com: markRaw(A)
},
{
name: "B组件",
com: markRaw(B)
},
{
name: "C组件",
com: markRaw(C)
}
])17、插槽slot
- 插槽就是子组件中的提供给父组件使用的一个占位符,用
表示,父组件可以在这个占位符中填充任何模板代码,如 HTML、组件等,填充的内容会替换子组件的 标签。
17-1、匿名插槽
- 父组件 在父组件给这个插槽填充内容
1
2
3
4
5
6
7
8
9
10
11<template>
<Son>
<template v-slot>
<div>aaa</div>
</template>
</Son>
</template>
<script setup lang='ts'>
import Son from './components/Son.vue';
</script> - 在子组件放置一个插槽
1
2
3
4<template>
<!-- 通过slot接收父组件插入的内容 -->
<slot></slot>
</template>
17-2、具名插槽
具名插槽其实就是给插槽取个名字。一个子组件可以放多个插槽,而且可以放在不同的地方,而父组件填充内容时,可以根据这个名字把内容填充到对应插槽中
子组件在slot标签中起名字
1
2
3<template>
<slot name="bbb"></slot>
</template>父组件使用对应名称
1
2
3
4
5
6
7<template>
<Son>
<template v-slot:bbb>
<div>aaa</div>
</template>
</Son>
</template>
17-3、作用域插槽
在子组件动态绑定参数 派发给父组件的slot去使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27<template>
<div v-for="item in data">
<slot :list="item"></slot>
</div>
</template>
<script setup lang='ts'>
import {reactive} from "vue"
type names={
name:string,
age:number
}
const data = reactive<names[]>([
{
name:"a1",
age:18
},
{
name:"a2",
age:17
},
{
name:"a3",
age:16
}
])
</script>父组件在slot标签中通过v-slot取值
1
2
3
4
5
6
7<template>
<Son>
<template v-slot="{list}">
<div>{{ list }}</div>
</template>
</Son>
</template>
17-4、动态插槽
插槽可以是一个变量名
父组件
1
2
3
4
5
6
7
8
9
10
11
12
13<template>
<Son>
<template #[name]>
<div>我是谁 我在那</div>
</template>
</Son>
</template>
<script setup lang='ts'>
import { ref } from 'vue';
import Son from './components/Son.vue';
let name = ref('zzm')
</script>子组件
1
2
3<template>
<slot name="zzm"></slot>
</template>
18、异步组件&代码分包&Suspense
异步组件Sync
- 顶层await 可以让后续的代码变为异步
1
2
3
4
5
6
7
8
9
10
11
12<template>
<div v-for="item in list" :key="item.goods_id">{{ item.title }}</div>
</template>
<script setup lang='ts'>
// ES7之后可以使用顶层await方法 await 后面都是异步方法
const { list } = await fetch(" http://localhost:8888/goods/list").then((res) => {
return res.json().then((res) => {
return res
})
})
</script>
父组件
- 父组件引用子组件 通过defineAsyncComponent加载异步配合import 函数模式便可以分包
- suspense 组件有两个插槽。它们都只接收一个直接子节点。default 插槽里的节点会尽可能展示出来。如果不能,则展示 fallback 插槽里的节点。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16<template>
<Suspense >
<template #default>
<Sync/>
</template>
<template #fallback>
<div>loading....</div>
</template>
</Suspense>
</template>
<script setup lang='ts'>
import {defineAsyncComponent} from 'vue';
const Sync = defineAsyncComponent(()=>import('./components/Sync.vue'))
</script>
19、Teleport传送组件
- Teleport Vue 3.0新特性之一。
- Teleport 是一种能够将我们的模板渲染至指定DOM节点,不受父级style、v-show等属性影响,但data、prop数据依旧能够共用的技术;类似于 React 的 Portal。
- 主要解决的问题 因为Teleport节点挂载在其他指定的DOM节点下,完全不受父级style样式影响
19-1、使用方法
- 通过to 属性 插入指定元素位置 to=”body” 便可以将Teleport 内容传送到指定位置
1
2
3
4
5
6
7
8
9
10<template>
<div class="box1"></div>
<Teleport to="body">
<A />
</Teleport>
</template>
<script setup lang='ts'>
import A from './components/A.vue';
</script> - 如图所示 它被传送到body下面
19-2、动态控制teleport
- 使用disabled 设置为 true则 to属性不生效 false 则生效
1
2
3
4
5
6
7
8
9
10<template>
<div class="box1"></div>
<Teleport :disabled="true" to=".box1">
<A />
</Teleport>
</template>
<script setup lang='ts'>
import A from './components/A.vue';
</script> - 我们给它传送到类名为.box1的元素下 但是因为我们:disabled=”true” 所以to不生效 它还在body下面
20、keep-alive缓存组件
20-1、内置组件keep-alive
有时候我们不希望组件被重新渲染影响使用体验;或者处于性能考虑,避免多次重复渲染降低性能。而是希望组件可以缓存下来,维持当前的状态。这时候就需要用到keep-alive组件。
开启keep-alive 生命周期的变化
初次进入时: onMounted> onActivated
退出后触发 deactivated
再次进入:
只会触发 onActivated
事件挂载的方法等,只执行一次的放在 onMounted中;组件每次进去执行的方法放在 onActivated中1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<template>
<button @click="flag=!flag">切换组件</button>
<keep-alive>
<A v-if="flag"/>
<B v-else/>
</keep-alive>
</template>
<script setup lang='ts'>
import { ref } from 'vue';
import A from './components/A.vue';
import B from './components/B.vue';
const flag = ref<boolean>(true)
</script>
20-2、 include 和 exclude 和 max
- include 和 exclude 允许组件有条件地缓存。二者都可以用逗号分隔字符串、正则表达式或一个数组来表示:
include
- include表示缓存哪个组件
1
2
3
4
5
6
7
8<template>
<button @click="flag=!flag">切换组件</button>
<!-- :include="['A']" 这段代码的意思就是 只缓存A组件 不缓存B组件 -->
<keep-alive :include="['A']">
<A v-if="flag"/>
<B v-else/>
</keep-alive>
</template>
exclude
- exclude表示不缓存哪个组件
1
2
3
4
5
6
7
8<template>
<button @click="flag=!flag">切换组件</button>
<!-- :exclude="['A']" 这段代码的意思就是 不缓存A组件 -->
<keep-alive :exclude="['A']">
<A v-if="flag"/>
<B v-else/>
</keep-alive>
</template>
max
- max表示你最多缓存组件的数量
1
2
3
4
5
6
7
8<template>
<button @click="flag=!flag">切换组件</button>
<!-- :max="10" 这段代码的意思就是 只缓存10个组件 它的算法是剔除不常用的组件 保留十个最活跃的组件 -->
<keep-alive :max="10">
<A v-if="flag"/>
<B v-else/>
</keep-alive>
</template>
20-3、 keep-alive开启后的生命周期(onActivated和onDeactiveted)
- 只有被keep-alive标签包裹的组件才有这两个生命周期钩子
1
2
3
4
5
6
7
8
9
10
11
12
13import {onMounted,onActivated,onDeactivated} from 'vue'
// 这个生命周期只会执行一次
onMounted(()=>{
console.log("初始化")
})
// 每次进入该组件这个生命周期都会执行
onActivated(()=>{
console.log("keep-alive初始化")
})
// 每次离开该组件这个生命周期都会执行
onDeactivated(()=>{
console.log("keep-alive卸载")
})21、Transition动画组件
- Vue 提供了 transition 的封装组件,在下列情形中,可以给任何元素和组件添加进入/离开过渡:
- 条件渲染 (使用 v-if)
- 条件展示 (使用 v-show)
- 动态组件
- 组件根节点
- 自定义 transition 过度效果,你需要对transition组件的name属性自定义。并在css中写入对应的样式
21-1、过渡的类名
- 在进入/离开的过渡中,会有 6 个 class 切换。
过渡 class
v-enter-from:定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除。
v-enter-active:定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数。
v-enter-to:定义进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时 v-enter-from 被移除),在过渡/动画完成之后移除。
v-leave-from:定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除。
v-leave-active:定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数。
v-leave-to:离开过渡的结束状态。在离开过渡被触发之后下一帧生效 (与此同时 v-leave-from 被移除),在过渡/动画完成之后移除。
如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47<template>
<button @click="xxx">切换</button>
<Transition name="fade">
<div v-if="flag" class="content"></div>
</Transition>
</template>
<style scoped>
.content{
width: 200px;
height: 200px;
background-color: red;
}
/* 开始过度 */
.fade-enter-from{
background:red;
width:0px;
height:0px;
transform:rotate(360deg)
}
/* 开始过度了 */
.fade-enter-active{
transition: all 2.5s linear;
}
/* 过度完成 */
.fade-enter-to{
background:yellow;
width:200px;
height:200px;
}
/* 离开的过度 */
.fade-leave-from{
width:200px;
height:200px;
transform:rotate(360deg)
}
/* 离开中过度 */
.fade-leave-active{
transition: all 1s linear;
}
/* 离开完成 */
.fade-leave-to{
width:0px;
height:0px;
}
</style>
21-2、自定义过渡class类名
trasnsition props
- enter-from-class
- enter-active-class
- enter-to-class
- leave-from-class
- leave-active-class
- leave-to-class
如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18<Transition enter-from-class="aaa" enter-active-class="bbb" enter-to-class="ccc">
<div v-if="flag" class="content"></div>
</Transition>
<style>
.aaa{
width: 0;
height: 0;
}
.bbb{
transition: all 1.5s ease;
}
.ccc{
width: 200px;
height: 200px;
transform: rotate(360deg);
}
<style>
21-3、自定义过渡时间 单位毫秒
- 你也可以分别指定进入和离开的持续时间:
1
2
3
4<!-- 表示动画在1000毫秒内执行完 -->
<transition :duration="1000">...</transition>
<!-- 表示进入动画在500毫秒内执行完 离开动画在800毫秒内执行完 -->
<transition :duration="{ enter: 500, leave: 800 }">...</transition>
21-4、结合自定义动画库
- 通过自定义class 结合css动画库animate css
- 安装库 npm install animate.css
- 引入 import ‘animate.css’
- 使用方法
官方文档 https://animate.style/1
2
3
4
5
6<transition
leave-active-class="animate__animated animate__bounceInLeft"
enter-active-class="animate__animated animate__bounceInRight"
>
<div v-if="flag" class="box"></div>
</transition>
21-5、transition 生命周期8个
@before-enter=”beforeEnter” 对应enter-from
@enter=”enter” 对应enter-active
@after-enter=”afterEnter” 对应enter-to
@enter-cancelled=”enterCancelled” 显示过度打断
@before-leave=”beforeLeave” 对应leave-from
@leave=”leave” 对应enter-active
@after-leave=”afterLeave” 对应leave-to
@leave-cancelled=”leaveCancelled” 离开过度打断
当只用 JavaScript 过渡的时候,在 enter 和 leave 钩子中必须使用 done 进行回调
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46<template>
<Transition
@before-enter="beforeEnter"
@enter="enter"
@after-enter="afterEnter"
@enter-cancelled="enterCancelled"
@before-leave="beforeLeave"
@leave="leave"
@after-leave="afterLeave"
@leave-cancelled="leaveCancelled"
>
</Transition>
</template>
<script setup lang='ts'>
const beforeEnter=(el:Element)=>{
console.log("进入之前",el)
}
const enter=(el:Element,done:Function)=>{
console.log("过渡曲线",el)
done()
}
const afterEnter=(el:Element)=>{
console.log("过渡完成",el)
}
const enterCancelled=(el:Element)=>{
console.log("过渡效果被打断",el)
}
const beforeLeave=(el:Element)=>{
console.log("离开之前",el)
}
const leave=(el:Element,done:Function)=>{
console.log("离开过渡曲线",el)
setTimeout(()=>{
done()
},2000)
}
const afterLeave=(el:Element)=>{
console.log("离开过渡完成",el)
}
const leaveCancelled=(el:Element)=>{
console.log("离开过渡效果被打断",el)
}
</script>
21-6、appear
通过这个属性可以设置初始节点过度 就是页面加载完成就开始动画 对应三个状态
页面展示会执行该动画
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21<Transition appear-active-class="active" appear-from-class="from" appear-to-class="to" appear>
<div v-if="flag" class="content"></div>
</Transition>
<style scoped>
.content {
width: 200px;
height: 200px;
background-color: red;
}
.from{
width: 0;
height: 0;
}
.active{
transition: all 2s ease;
}
.to{
width: 200px;
height: 200px;
}
</style>
22、Transition-group
- 单个节点
- 多个节点,每次只渲染一个
- 那么怎么同时渲染整个列表,比如使用 v-for?在这种场景下,我们会使用
组件。在我们深入例子之前,先了解关于这个组件的几个特点: - 默认情况下,它不会渲染一个包裹元素,但是你可以通过 tag attribute 指定渲染一个元素。
- 过渡模式不可用,因为我们不再相互切换特有的元素。
- 内部元素总是需要提供唯一的 key attribute 值。
- CSS 过渡的类将会应用在内部的元素中,而不是这个组/容器本身。
22-1、过渡列表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26<!-- 配合animate.css去使用 -->
<template>
<div class="content">
<button @click="add">ADD</button>
<button @click="del">DEL</button>
<transition-group
class="wraps"
leave-active-class="animate__animated animate__hinge"
enter-active-class="animate__animated animate__bounceInUp"
>
<div class="item" v-for="item in list" :key="item">{{ item }}</div>
</transition-group>
</div>
</template>
<script setup lang='ts'>
import { reactive } from 'vue'
import 'animate.css';
const list = reactive<number[]>([1, 2, 3, 4, 5])
const add = () => {
list.push(list.length + 1)
}
const del = () => {
list.pop()
}
</script>22-2、列表的移动过渡
组件还有一个特殊之处。除了进入和离开,它还可以为定位的改变添加动画。只需了解新增的 v-move 类就可以使用这个新功能,它会应用在元素改变定位的过程中。像之前的类名一样,它的前缀可以通过 name attribute 来自定义,也可以通过 move-class attribute 手动设置 如下代码
我们需要下载npm i –save lodash这个库
还有npm i @types/lodash -D
官方文档 https://www.lodashjs.com/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44<template>
<div>
<button @click="random">random</button>
<transition-group move-class="mmm" class="wraps" tag="div">
<div class="items" v-for="item in list" :key="item.id">{{ item.number }}</div>
</transition-group>
</div>
</template>
<script setup lang='ts'>
import { ref } from 'vue'
import _ from "lodash"
let list = ref(Array.apply(null,{length:81} as number[]).map((_,index)=>{
return {
id:index,
number:(index % 9)+1
}
}))
console.log(list)
const random=()=>{
list.value = _.shuffle(list.value)
}
</script>
<style scoped>
.content {
width: 100%;
}
.wraps {
display: flex;
flex-wrap: wrap;
width: calc(25px * 10 + 9px);
}
.items {
width: 25px;
height: 25px;
border: 1px solid #000;
display: flex;
justify-content: center;
align-items: center;
}
.mmm{
transition: all 1s;
}
</style>
22-3、状态过渡
- Vue 也同样可以给数字 Svg 背景颜色等添加过度动画 今天演示数字变化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25<template>
<div>
<input v-model="num.current" type="number" step="20">
<div>
{{ num.tweenedNumber }}
</div>
</div>
</template>
<script setup lang='ts'>
import gsap from "gsap"
import { reactive,watch} from "vue";
const num = reactive({
current:0,
tweenedNumber:0
})
watch(()=>num.current,(newVal)=>{
gsap.to(num,{
duration:1,
tweenedNumber:newVal,
})
})
</script>
23、依赖注入Provide / Inject
通常,当我们需要从父组件向子组件传递数据时,我们使用 props。想象一下这样的结构:有一些深度嵌套的组件,而深层的子组件只需要父组件的部分内容。在这种情况下,如果仍然将 prop 沿着组件链逐级传递下去,可能会很麻烦。
provide 可以在祖先组件中指定我们想要提供给后代组件的数据或方法,而在任何后代组件中,我们都可以使用 inject 来接收 provide 提供的数据或方法
父组件 通过provide分发数据 provide(事件名称,数据)
如果父组件不希望子组件和后台组件修改数据 可以使用readonly将数据设为只读的provide(‘名称’, readonly(flag))
1
2
3
4
5
6
7
8
9
10
11
12
13
14<template>
<div class="box">
<h1>我是父级{{ flag }}</h1>
<A />
</div>
</template>
<script setup lang='ts'>
import A from './components/A.vue';
import { provide, ref } from 'vue'
let flag = ref<string>("ZZM")
provide('flag', flag)
</script>子组件 通过inject接收 inject(provide定义的名称,使用ref接收 将数据变为响应式)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19<template>
<div class="box1">
<h2>我是子组件{{ flag }}</h2>
<button @click="change">子组件修改</button>
<B />
</div>
</template>
<script setup lang='ts'>
import B from "./B.vue"
import { inject, Ref, ref } from 'vue'
// Ref是ref的TS类型
const flag = inject<Ref<string>>('flag', ref(""))
const change = () => {
flag.value = "赵四眼"
}
</script>后代组件 通过inject接收 inject(provide定义的名称,使用ref接收 将数据变为响应式)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16<template>
<div class="box2">
<h3>我是孙子组件{{ flag }}</h3>
<button @click="change">孙子组件修改</button>
</div>
</template>
<script setup lang='ts'>
import { inject, Ref, ref } from 'vue'
// Ref是ref的TS类型
const flag = inject<Ref<string>>('flag', ref(""))
const change = () => {
flag.value = "赵小眼"
}
</script>页面效果
24、兄弟组件传参和Bus
24-1、借助父组件传参
例如父组件为App 子组件为A 和 B他两个是同级的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16<template>
<div>
<A @on-click="getFalg"></A>
<B :flag="Flag"></B>
</div>
</template>
<script setup lang='ts'>
import A from './components/A.vue'
import B from './components/B.vue'
import { ref } from 'vue'
let Flag = ref<boolean>(false)
const getFalg = (flag: boolean) => {
Flag.value = flag;
}
</script>子组件A
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<template>
<div class="box1">
<h2>我是子组件</h2>
<button @click="emitB">派发一个事件</button>
</div>
</template>
<script setup lang='ts'>
const emit = defineEmits(['aaa'])
let flag = true
const emitB=()=>{
flag = !flag
emit("aaa",flag)
}
</script>子组件B
1
2
3
4
5
6
7
8
9
10
11
12
13
14<template>
<div class="box2">
<h3>我是孙子组件</h3>
{{ flag }}
</div>
</template>
<script setup lang='ts'>
</script>A 组件派发事件通过App.vue 接受A组件派发的事件然后在Props 传给B组件 也是可以实现的
缺点就是比较麻烦 ,无法直接通信,只能充当桥梁
24-2、手写Event Bus
我们在Vue2 可以使用$emit 传递 $on监听 emit传递过来的事件
这个原理其实是运用了JS设计模式之发布订阅模式
Bus.ts Bus.ts文件放在src目录下即可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26type BusClass = {
emit: (name: string) => void
on: (name: string, callback: Function) => void
}
type BusParams = string | number | symbol
type List = {
[key: BusParams]: Array<Function>
}
class Bus implements BusClass {
list: List
constructor() {
this.list = {}
}
emit(name: string, ...args: Array<any>) {
let eventName: Array<Function> = this.list[name]
eventName.forEach(ev => {
ev.apply(this, args)
})
}
on(name: string, callback: Function) {
let fn: Array<Function> = this.list[name] || [];
fn.push(callback)
this.list[name] = fn
}
}
export default new Bus()然后挂载到Vue config 全局就可以使用啦
App.vue组件
1
2
3
4
5
6
7
8
9
10
11
12<template>
<div class="box">
<h1>我是父级</h1>
<A/>
<B/>
</div>
</template>
<script setup lang='ts'>
import A from './components/A.vue';
import B from "./components/B.vue"
</script>A.vue组件 负责派发给兄弟组件数据 通过Bus.emit(“名称”,数据)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<template>
<div class="box1">
<h2>我是子组件</h2>
<button @click="emitB">派发一个事件</button>
</div>
</template>
<script setup lang='ts'>
import Bus from "../Bus"
let flag = true
const emitB=()=>{
flag = !flag
Bus.emit("aaa",flag)
}
</script>B.vue组件 接收兄弟组件A派发的数据 通过Bus.on(“名称”,回调函数)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<template>
<div class="box2">
<h3>我是孙子组件</h3>
{{ flag }}
</div>
</template>
<script setup lang='ts'>
import { ref } from "vue";
import Bus from "../Bus"
let flag = ref<boolean>(false)
Bus.on("aaa",(e:boolean)=>{
flag.value = e
})
</script>
24-3、使用库实现Event Bus
安装
1
npm install mitt -S
main.ts 初始化
- 全局总线,vue 入口文件 main.js 中挂载全局属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18import { createApp } from 'vue'
import App from './App.vue'
import mitt from 'mitt'
const Mit = mitt()
//TypeScript注册
// 由于必须要拓展ComponentCustomProperties类型才能获得类型提示
declare module "vue" {
export interface ComponentCustomProperties {
$Bus: typeof Mit
}
}
const app = createApp(App)
//Vue3挂载全局API
app.config.globalProperties.$Bus = Mit
app.mount('#app')
使用方法通过emit派发, on 方法添加事件,off 方法移除,clear 清空所有
A组件派发(emit)emit(自定义事件名称,数据)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18<template>
<div>
<h1>我是A</h1>
<button @click="emit1">emit1</button>
<button @click="emit2">emit2</button>
</div>
</template>
<script setup lang='ts'>
import { getCurrentInstance } from 'vue'
const instance = getCurrentInstance();
const emit1 = () => {
instance?.proxy?.$Bus.emit('on-num', 100)
}
const emit2 = () => {
instance?.proxy?.$Bus.emit('*****', 500)
}
</script>B组件监听(on) on(自定义事件名称,回调函数)
1
2
3
4
5
6
7
8
9
10
11
12
13<template>
<div>
<h1>我是B</h1>
</div>
</template>
<script setup lang='ts'>
import { getCurrentInstance } from 'vue'
const instance = getCurrentInstance()
instance?.proxy?.$Bus.on('on-num', (num) => {
console.log(num,'===========>B')
})
</script>监听所有事件( on(“*”) )
1
2
3instance?.proxy?.$Bus.on('*',(type,num)=>{
console.log(type,num,'===========>B')
})移除监听事件(off) off(取消指定的mitt事件,取消的函数)
1
2
3
4
5const Fn = (num: any) => {
console.log(num, '===========>B')
}
instance?.proxy?.$Bus.on('on-num',Fn)//listen
instance?.proxy?.$Bus.off('on-num',Fn)//unListen清空所有监听(clear)clear()取消所有的mitt事件
1
instance?.proxy?.$Bus.all.clear()
25、TSX
- 我们之前呢是使用Template去写我们模板。现在可以扩展另一种风格TSX风格
- vue2 的时候就已经支持jsx写法,只不过不是很友好,随着vue3对typescript的支持度,tsx写法越来越被接受
25-1、安装插件
1
npm install @vitejs/plugin-vue-jsx -D
vite.config.ts 配置
1
2
3
4
5
6
7
8import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// 引入tsx
import vueJsx from '@vitejs/plugin-vue-jsx';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue(),vueJsx()] // 挂载
})修改tsconfig.json 配置文件
1
2
3"jsx": "preserve",
"jsxFactory": "h",
"jsxFragmentFactory": "Fragment",配置完成就可以使用啦
在目录新建一个xxxxxx.tsx文件
25-2、使用TSX
- TIPS tsx不会自动解包 使用ref加.vlaue ! ! !
tsx的三种写法
第一种 返回一个渲染函数
1
2
3export default function A() {
return (<div></div>)
}第二种 使用optionsAPI
1
2
3
4
5
6
7
8
9
10
11
12
13import {defineComponent} from "vue"
export default defineComponent({
data(){
return {
age:23
}
},
render(){
return (<div>{this.age}</div>)
}
})第三种 setup函数模式
1
2
3
4
5
6
7import {defineComponent} from "vue"
export default defineComponent({
setup(){
return ()=>(<div></div>)
}
})
tsx支持 v-model 的使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14import { ref } from 'vue'
let v = ref<string>('')
const renderDom = () => {
return (
<>
<input v-model={v.value} type="text" />
<div>
{v.value}
</div>
</>
)
}
export default renderDomv-show
1
2
3
4
5
6
7
8
9
10
11
12import { ref } from 'vue'
let flag = ref(false)
const renderDom = () => {
return (
<>
<div v-show={flag.value}>景天</div>
<div v-show={!flag.value}>雪见</div>
</>
)
}
export default renderDomv-if是不支持的
- 所以需要改变风格 使用三元运算符来代替
1
2
3
4
5
6
7
8
9
10
11
12
13import { ref } from 'vue'
let flag = ref(false)
const renderDom = () => {
return (
<>
{
flag.value ? <div>景天</div> : <div>雪见</div>
}
</>
)
}
export default renderDom
v-for也是不支持的
- 需要使用map
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15import { ref } from 'vue'
let arr = [1,2,3,4,5]
const renderDom = () => {
return (
<>
{
arr.map(v=>{
return <div>${v}</div>
})
}
</>
)
}
export default renderDom
v-bind使用
- 直接赋值就行 不再使用v-bind
1
2
3
4
5
6
7
8
9
10
11import { ref } from 'vue'
let arr = [1, 2, 3, 4, 5]
const renderDom = () => {
return (
<>
<div data-arr={arr}>1</div>
</>
)
}
export default renderDom
v-on绑定事件 所有的事件都按照react风格来
- 所有事件有on开头
- 所有事件名称首字母大写
1
2
3
4
5
6
7
8
9
10
11const renderDom = () => {
return (
<>
<button onClick={clickTap}>点击</button>
</>
)
}
const clickTap = () => {
console.log('click');
}
export default renderDomProps 接受值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17import { ref } from 'vue'
type Props = {
title:string
}
const renderDom = (props:Props) => {
return (
<>
<div>{props.title}</div>
<button onClick={clickTap}>点击</button>
</>
)
}
const clickTap = () => {
console.log('click');
}
export default renderDomEmit派发
1
2
3
4
5
6
7
8
9
10
11
12
13
14type Props = {
title: string
}
const renderDom = (props: Props,content:any) => {
return (
<>
<div>{props.title}</div>
<button onClick={clickTap.bind(this,content)}>点击</button>
</>
)
}
const clickTap = (ctx:any) => {
ctx.emit('on-click',1)
}Slot
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48const A = (props, { slots }) => (
<>
<h1>{ slots.default ? slots.default() : 'foo' }</h1>
<h2>{ slots.bar?.() }</h2>
</>
);
const App = {
setup() {
const slots = {
bar: () => <span>B</span>,
};
return () => (
<A v-slots={slots}>
<div>A</div>
</A>
);
},
};
// or
const App = {
setup() {
const slots = {
default: () => <div>A</div>,
bar: () => <span>B</span>,
};
return () => <A v-slots={slots} />;
},
};
// or you can use object slots when `enableObjectSlots` is not false.
const App = {
setup() {
return () => (
<>
<A>
{{
default: () => <div>A</div>,
bar: () => <span>B</span>,
}}
</A>
<B>{() => "foo"}</B>
</>
);
},
};小彩蛋Vue3自动引入插件
下载命令
1
npm i -D unplugin-auto-import
配置
在vite.config.ts中
1
2
3
4
5
6
7
8
9// 引入
import AutoImport from 'unplugin-auto-import/vite'
export default defineConfig({
plugins: [vue(),vueJsx(),AutoImport({
import:['vue'],
dts:'src/auto-import.d.ts'
})],
})配置完成之后使用ref reactive watch 等 无须import 导入 可以直接使用
GitHub - antfu/unplugin-auto-import: Auto import APIs on-demand for Vite, Webpack and Rollup
26、深入v-model
- TIps 在Vue3 v-model 是破坏性更新的
- v-model在组件里面也是很重要的
- v-model 其实是一个语法糖 通过props 和 emit组合而成的
- 1.默认值的改变
prop:value -> modelValue;
事件:input -> update:modelValue;
v-bind 的 .sync 修饰符和组件的 model 选项已移除
新增 支持多个v-model
新增 支持自定义 修饰符 Modifiers
案例1 子组件控制父组件状态
- 父组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18<template>
<div>
<h1>我是App父组件</h1>
{{ isShow }}
<div><button @click="xxx">开关</button></div>
<hr>
<Model v-model="isShow"/>
</div>
</template>
<script setup lang='ts'>
import { ref } from "vue";
import Model from "./components/Model.vue";
const isShow = ref<boolean>(true)
const xxx=()=>{
isShow.value = !isShow.value
}
</script> - 子组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21<template>
<div v-if="modelValue" class="model">
<div class="close"><button @click="close">关闭</button></div>
<h3>我是model子组件</h3>
<div>内容:<input type="text" /></div>
</div>
</template>
<script setup lang='ts'>
// 子组件接收父组件传来的状态
defineProps<{
modelValue: boolean,
}>()
// 子组件创建emit 固定写法['update:父组件传来的名字']
const emit = defineEmits(['update:modelValue'])
const close=()=>{
// 子组件通过emit将状态传给父组件
emit("update:modelValue",false)
}
</script>
案例2 绑定多个案例
父组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<template>
<div>
<h1>我是App父组件</h1>
<div>test:{{ test }}</div>
<hr>
<Model v-model:textValue="test"/>
</div>
</template>
<script setup lang='ts'>
import { ref } from "vue";
import Model from "./components/Model.vue";
const test = ref<string>("赵四眼")
</script>子组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22<template>
<div class="model">
<h3>我是model子组件</h3>
<div>内容:<input @input="change" :value="textValue" type="text" /></div>
</div>
</template>
<script setup lang='ts'>
// 子组件接收父组件传来的状态
defineProps<{
textValue:string
}>()
// 子组件创建emit 固定写法['update:父组件传来的名字']
const emit = defineEmits(["update:textValue"])
// 子组件修改内容 通过emit 同步到父组件
const change=(e:any)=>{
emit("update:textValue",e.target.value)
}
</script>
27、directive-自定义指令(属于破坏性更新)
- Vue中有v-if,v-for,v-bind,v-show,v-model 等等一系列方便快捷的指令 今天一起来了解一下vue里提供的自定义指令
27-1、 Vue3指令的钩子函数
- created 元素初始化的时候
- beforeMount 指令绑定到元素后调用 只调用一次
- mounted 元素插入父级dom调用
- beforeUpdate 元素被更新之前调用
- update 这个周期方法被移除 改用updated
- beforeUnmount 在元素被移除前调用
- unmounted 指令被移除后调用 只调用一次
Vue2 指令 bind inserted update componentUpdated unbind
27-2、在setup内定义局部指令
但这里有一个需要注意的限制:必须以 vNameOfDirective 的形式来命名本地自定义指令,以使得它们可以直接在模板中使用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52<template>
<div>
<button @click="flag = !flag">切换</button>
<A v-if="flag" v-move="{background:'red'}"/>
</div>
</template>
<script setup lang='ts'>
import { DirectiveBinding } from 'vue';
import A from './components/A.vue';
import {ref,Directive} from 'vue'
let flag = ref<boolean>(true)
type Dir = {
background: string
}
const vMove:Directive = {
// 初始化元素的时候
created(){
console.log("======>created")
},
// 指令绑定到元素后调用 只会调用一次
beforeMount(){
console.log("======>beforeMount")
},
// 元素插入父级dom调用
mounted(el:HTMLElement,binding:DirectiveBinding<Dir>){
console.log("======>mounted")
console.log(el)
console.log(binding)
// 在元素上做些操作 更改A组件中div的背景颜色
el.style.background = binding.value.background
},
// 元素被更新之前调用
beforeUpdate(){
console.log("======>beforeUpdate")
},
// 元素更新后调用
updated(){
console.log("======>updated")
},
// 在元素被移除前调用
beforeUnmount(){
console.log("======>beforeUnmount")
},
// 指令被移除后调用 只会调用一次
unmounted(){
console.log("======>unmounted")
}
}
</script>- 我们看看el和binding打印的是什么
- 第一个 el 当前绑定的DOM 元素
- 第二个 binding
instance:使用指令的组件实例。
value:传递给指令的值。例如,在 v-my-directive=”1 + 1” 中,该值为 2。
oldValue:先前的值,仅在 beforeUpdate 和 updated 中可用。无论值是否有更改都可用。
arg:传递给指令的参数(如果有的话)。例如在 v-my-directive:foo 中,arg 为 “foo”。
modifiers:包含修饰符(如果有的话) 的对象。例如在 v-my-directive.foo.bar 中,修饰符对象为 {foo: true,bar: true}。
dir:一个对象,在注册指令时作为参数传递 - 第三个 当前元素的虚拟DOM 也就是Vnode
- 第四个 prevNode 上一个虚拟节点,仅在 beforeUpdate 和 updated 钩子中可用
27-3、函数简写
- 你可能想在 mounted 和 updated 时触发相同行为,而不关心其他的钩子函数。那么你可以通过将这个函数模式实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18<template>
<div>
<input v-model="value" type="text" />
<A v-move="{ background: value }"></A>
</div>
</template>
<script setup lang='ts'>
import A from './components/A.vue'
import { ref, Directive, DirectiveBinding } from 'vue'
let value = ref<string>('')
type Dir = {
background: string
}
const vMove: Directive = (el, binding: DirectiveBinding<Dir>) => {
el.style.background = binding.value.background
}
</script>
27-4、案例
案例权限按钮
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33<template>
<div class="btn">
<button v-has-show="'shop:create'">创建</button>
<button v-has-show="'shop:edit'">编辑</button>
<button v-has-show="'shop:del'">删除</button>
</div>
</template>
<script setup lang='ts'>
import type {Directive} from "vue"
localStorage.setItem("user",'zzmSB')
const permission = [
'zzmSB:shop:edit',
'zzmSB:shop:create',
'zzmSB:shop:del',
]
const userId = localStorage.getItem("user") as string
const vHasShow:Directive<HTMLElement,string> = (el,binding)=> {
console.log(el,binding)
if(!permission.includes(userId+":"+binding.value)){
el.style.display = 'none'
}
}
</script>
<style scoped>
button{
margin-left: 10px;
}
</style>案例自定义拖拽指令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52<template>
<div v-move class="box">
<div class="header"></div>
<div>
内容
</div>
</div>
</template>
<script setup lang='ts'>
import type { Directive } from "vue"
const vMove: Directive<HTMLElement, string> = (el, binding) => {
let moveElement = el.firstElementChild as HTMLDivElement
console.log(moveElement)
// 鼠标左键按下事件
const mouseDown = (e:MouseEvent) =>{
let X = e.clientX - el.offsetLeft // 距离浏览器可视窗口的左边的坐标值 - 获取元素偏移量(如果父级元素都没有定位 偏移量相对于body)
let Y = e.clientY - el.offsetTop // 距离浏览器可视窗口的上边的坐标值 - 获取元素偏移量(如果父级元素都没有定位 偏移量相对于body)
// 鼠标在元素内移动触发事件
const move = (e:MouseEvent) =>{
console.log(e)
el.style.left = e.clientX - X + "px"
el.style.top = e.clientY - Y + "px"
}
document.addEventListener('mousemove',move)
// 鼠标左键抬起事件
document.addEventListener("mouseup",()=>{
document.removeEventListener('mousemove',move)
})
}
moveElement.addEventListener("mousedown",mouseDown)
}
</script>
<style scoped>
.box {
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 200px;
height: 200px;
border: 1px solid #ccc;
.header {
height: 20px;
background: black;
cursor: move;
}
}
</style>案例图片懒加载
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30<template>
<div>
<div>
<img v-lazy="item" v-for="item in arr" :src="item" style="width:100%">
</div>
</div>
</template>
<script setup lang='ts'>
import { Directive } from 'vue';
// glob 是懒加载的模式
// globEager 静态加载
let imageList:Record<string,{default:string}> = import.meta.globEager('./assets/image/*.*')
let arr = Object.values(imageList).map(v=>v.default)
let vLazy:Directive<HTMLImageElement,string>= async(el,binding) =>{
const def = await import('./assets/vue.svg') // 加载一张默认图片
el.src = def.default // 给元素赋值默认图片
const observer = new IntersectionObserver((enr)=>{ // 监听元素所在视图位置
if(enr[0].intersectionRatio > 0){ // intersectionRatio为1 表示元素在视图中心
setTimeout(()=>{
el.src = binding.value // 两秒后将图片路径赋值给元素
},2000)
observer.unobserve(el) // 停止监听
}
})
observer.observe(el) // 开始监听元素 el就是元素
}
</script>28、自定义Hooks
Vue3 自定义Hook
主要用来处理复用代码逻辑的一些封装
这个在vue2 就已经有一个东西是Mixins
mixins就是将这些多个相同的逻辑抽离出来,各个组件只需要引入mixins,就能实现一次写代码,多组件受益的效果。
弊端就是 会涉及到覆盖的问题
组件的data、methods、filters会覆盖mixins里的同名data、methods、filters。第二点就是 变量来源不明确(隐式传入),不利于阅读,使代码变得难以维护。
Vue3 的自定义的hook
Vue3 的 hook函数 相当于 vue2 的 mixin, 不同在与 hooks 是函数
Vue3 的 hook函数 可以帮助我们提高代码的复用性, 让我们能在不同的组件中都利用 hooks 函数Vue3 hook 库 Get Started | VueUse
案例1、图片转base64
自定义Hooks文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27import {onMounted} from 'vue'
type Options={
el:string
}
// 图片转base64
export default function (options:Options):Promise<{baseUrl:string}>{
return new Promise((resolve)=>{
onMounted(()=>{
let img:HTMLImageElement = document.querySelector(options.el) as HTMLImageElement
console.log(img)
img.onload = () =>{
base64(img)
resolve({
baseUrl:base64(img)
})
}
})
const base64 =(el:HTMLImageElement) =>{
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
canvas.height = el.height
canvas.width = el.width
ctx?.drawImage(el, 0, 0, el.height, el.width)
return canvas.toDataURL('image/jpg')
}
})
}页面中使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14<template>
<div>
<img id="img" src="./assets//image/20237191458543768.jpg" alt="">
</div>
</template>
<script setup lang='ts'>
import useBase64 from "./hooks"
useBase64({
el:"#img"
}).then((res)=>{
console.log(res.baseUrl)
})
</script>
29、Vue3定义全局函数和变量
- globalProperties
- 由于Vue3 没有Prototype 属性 使用 app.config.globalProperties 代替 然后去定义变量和函数
全局变量
在main.ts文件中添加全局变量
1
2
3
4
5
6
7
8
9// 我们通过app.config.globalProperties.变量名 来定义全局变量
app.config.globalProperties.$zzm = "zzm"
// 声明文件 不然TS无法正确类型推导
declare module 'vue' {
export interface ComponentCustomProperties {
$zzm:string
}
}组件中使用
1
2
3
4
5
6
7
8
9
10
11
12<template>
<!-- 在视图中调用 -->
<div>{{ $zzm }}</div>
</template>
<script setup lang='ts'>
import { getCurrentInstance } from 'vue';
// 在js中调用
const app = getCurrentInstance()
console.log(app?.proxy?.$zzm)
</script>
全局方法
在main.ts中添加全局方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// 我们通过app.config.globalProperties.方法名 来定义全局方法
app.config.globalProperties.$filters = {
format<T>(str:T){
return `赵四眼${str}`
}
}
type Filter = {
format<T>(str: T): string
}
// 声明要扩充@vue/runtime-core包的声明.
// 这里扩充"ComponentCustomProperties"接口, 因为他是vue3中实例的属性的类型.
declare module 'vue' {
export interface ComponentCustomProperties {
$filters: Filter,
}
}在组件中使用
1
2
3
4
5
6
7
8
9
10
11<template>
<div>{{ $filters.format("是(⊙_⊙)?") }}</div>
</template>
<script setup lang='ts'>
import { getCurrentInstance } from 'vue';
// 在js中调用
const app = getCurrentInstance()
console.log(app?.proxy?.$filters.format("1111"))
</script>
30、编写Vue3插件
- 在使用 createApp() 初始化 Vue 应用程序后,你可以通过调用 use() 方法将插件添加到你的应用程序中。
- 实现一个Loading
Loading.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38<template>
<div v-if="isShow" class="loading">
<div class="loading-content">Loading...</div>
</div>
</template>
<script setup lang='ts'>
import { ref } from 'vue';
const isShow = ref(false)//定位loading 的开关
const show = () => {
isShow.value = true
}
const hide = () => {
isShow.value = false
}
//对外暴露 当前组件的属性和方法
defineExpose({
isShow,
show,
hide
})
</script>
<style scoped lang="less">
.loading {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.8);
display: flex;
justify-content: center;
align-items: center;
&-content {
font-size: 30px;
color: #fff;
}
}
</style>
Loading.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15import { createVNode, render, VNode, App } from 'vue';
import Loading from './index.vue'
export default {
install(app: App) {
//createVNode vue提供的底层方法 可以给我们组件创建一个虚拟DOM 也就是Vnode
const vnode: VNode = createVNode(Loading)
//render 把我们的Vnode 生成真实DOM 并且挂载到指定节点
render(vnode, document.body)
// Vue 提供的全局配置 可以自定义
app.config.globalProperties.$loading = {
show: () => vnode.component?.exposed?.show(),
hide: () => vnode.component?.exposed?.hide()
}
}
}Main.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18import Loading from './components/loading'
let app = createApp(App)
app.use(Loading)
type Lod = {
show: () => void,
hide: () => void
}
//编写ts loading 声明文件放置报错 和 智能提示
declare module '@vue/runtime-core' {
export interface ComponentCustomProperties {
$loading: Lod
}
}
app.mount('#app')使用方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16<template>
<div></div>
</template>
<script setup lang='ts'>
import { ref,reactive,getCurrentInstance} from 'vue'
const instance = getCurrentInstance()
// 通过instance.proxy.插件名.方法名调用
instance?.proxy?.$Loading.show()
setTimeout(()=>{
instance?.proxy?.$Loading.hide()
},5000)
// console.log(instance)
</script>31、详解Scoped和样式穿透
主要是用于修改很多vue常用的组件库(element, vant, AntDesigin),虽然配好了样式但是还是需要更改其他的样式 就需要用到样式穿透
scoped的原理
ue中的scoped 通过在DOM结构以及css样式上加唯一不重复的标记:data-v-hash的方式,以保证唯一(而这个工作是由过PostCSS转译实现的),达到样式私有化模块化的目的。
总结一下scoped三条渲染规则:
1、给HTML的DOM节点加一个不重复data属性(形如:data-v-123)来表示他的唯一性
2、在每句css选择器的末尾(编译后的生成的css语句)加一个当前组件的data属性选择器(如[data-v-123])来私有化样式
3、如果组件内部包含有其他组件,只会给其他组件的最外层标签加上当前组件的data属性PostCSS会给一个组件中的所有dom添加了一个独一无二的动态属性data-v-xxxx,然后,给CSS选择器额外添加一个对应的属性选择器来选择该组件中dom,这种做法使得样式只作用于含有该属性的dom——组件内部dom, 从而达到了’样式模块化’的效果
案例修改Element ui Input样式
发现没有生效
如果不写Scoped 就没问题
原因就是Scoped 搞的鬼 他在进行PostCss转化的时候把元素选择器默认放在了最后
- Vue 提供了样式穿透:deep() 他的作用就是用来改变 属性选择器的位置
32、css Style完整新特性
32-1、插槽选择器
A 组件定义一个插槽
1
2
3
4
5
6
7
8
9
10
11
12
13<template>
<div>
我是插槽
<slot></slot>
</div>
</template>
<script>
export default {}
</script>
<style scoped>
</style>在App.vue 引入
1
2
3
4
5
6
7
8
9
10
11
12
13
14<template>
<div>
<A>
<div class="a">私人定制div</div>
</A>
</div>
</template>
<script setup>
import A from "@/components/A.vue"
</script>
<style lang="less" scoped>
</style>在A组件修改class a 的颜色
1
2
3
4
5<style scoped>
.a{
color:red
}
</style>无效果
- 默认情况下,作用域样式不会影响到 slot 渲染出来的内容,因为它们被认为是父组件所持有并传递进来的。
- 解决方案 slotted
1
2
3
4
5<style scoped>
:slotted(.a) {
color:red
}
</style>
32-2、全局选择器
- 在之前我们想加入全局 样式 通常都是新建一个style 标签 不加scoped 现在有更优雅的解决方案
1
2
3
4
5
6
7
8<style>
div{
color:red
}
</style>
<style lang="less" scoped>
</style>
1
2
3
4
5<style lang="less" scoped>
:global(div){
color:red
}
</style>- 效果等同于上面
32-3、动态 CSS
单文件组件的 style 标签可以通过 v-bind 这一 CSS 函数将 CSS 的值关联到动态的组件状态上:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16<template>
<div class="div">
zzm是个弟弟
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
const red = ref<string>('red')
</script>
<style lang="less" scoped>
.div{
color:v-bind(red)
}
</style>如果是对象 v-bind 请加引号
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18<template>
<div class="div">
小满是个弟弟
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue"
const red = ref({
color:'pink'
})
</script>
<style lang="less" scoped>
.div {
color: v-bind('red.color');
}
</style>
33、event loop 和 nextTick
33-1、JS 执行机制
在我们学js 的时候都知道js 是单线程的如果是多线程的话会引发一个问题在同一时间同时操作DOM 一个增加一个删除JS就不知道到底要干嘛了,所以这个语言是单线程的但是随着HTML5到来js也支持了多线程webWorker 但是也是不允许操作DOM
单线程就意味着所有的任务都需要排队,后面的任务需要等前面的任务执行完才能执行,如果前面的任务耗时过长,后面的任务就需要一直等,一些从用户角度上不需要等待的任务就会一直等待,这个从体验角度上来讲是不可接受的,所以JS中就出现了异步的概念。
33-2、 同步任务
代码从上到下按顺序执行
33-3、异步任务
1.宏任务
- script(整体代码)、setTimeout、setInterval、UI交互事件、postMessage、Ajax
2.微任务
Promise.then catch finally、MutaionObserver、process.nextTick(Node.js 环境)
运行机制
所有的同步任务都是在主进程执行的形成一个执行栈,主线程之外,还存在一个”任务队列”,异步任务执行队列中先执行宏任务,然后清空当次宏任务中的所有微任务,然后进行下一个tick如此形成循环。
nextTick 就是创建一个异步任务,那么它自然要等到同步任务执行完成后才执行。
33-4、如何去理解Tick
- 例如我们显示器是60FPS
- 那浏览器绘制一帧就是1000 / 60 ≈ 16.6ms
- 那浏览器这一帧率做了什么
1.处理用户的事件,就是event 例如 click,input change 等。
2.执行定时器任务
3.执行 requestAnimationFrame
4.执行dom 的回流与重绘
5.计算更新图层的绘制指令
6.绘制指令合并主线程 如果有空余时间会执行 requestidlecallback - 所以 一个Tick 就是去做了这些事
代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107<template>
<div ref="box" class="wraps">
<div>
<div class="item" v-for="item in chatList">
<div>{{ item.name }}:</div>
<div>{{ item.message }}</div>
</div>
</div>
</div>
<div class="ipt">
<div>
<textarea v-model="ipt" type="text" />
</div>
<div>
<button @click="send">send</button>
</div>
</div>
<!-- <HelloWorld></HelloWorld> -->
</template>
<script setup lang='ts'>
import { reactive, ref, nextTick } from 'vue'
//next Tick
//60FPS 1000/60 = 16.7ms
// 1.处理用户的事件,就是event 例如 click,input change 等。
// 2.执行定时器任务
// 3.执行 requestAnimationFrame
// 4.执行dom 的回流与重绘
// 5.计算更新图层的绘制指令
// 6.绘制指令合并主线程 如果有空余时间会执行 requestidlecallback
// for (let i =0;i<1000;i++) {
// current.value = i
// }
let chatList = reactive([
{ name: '张三', message: "xxxxxxxxx" },
])
let box = ref<HTMLDivElement>()
let ipt = ref('')
//Vue 更新dom是异步的 数据更新是同步
//我们本次执行的代码是同步代码
//当我们操作dom 的时候发现数据读取的是上次的 就需要使用nextIick
const send = async () => {
chatList.push({
name: "小满",
message: ipt.value
})
//1.回调函数模式
// nextTick(()=>{
// box.value!.scrollTop = 99999999
// })
//2.async await 写法
await nextTick()
box.value!.scrollTop = 99999999
//ipt.value = ''
}
</script>
<style scoped lang='scss'>
.wraps {
margin: 10px auto;
width: 500px;
height: 400px;
overflow: auto;
overflow-x: hidden;
background: #fff;
border: 1px solid #ccc;
.item {
width: 100%;
height: 50px;
background: #ccc;
display: flex;
align-items: center;
padding: 0 10px;
border-bottom: 1px solid #fff;
}
}
.ipt {
margin: 10px auto;
width: 500px;
height: 40px;
background: #fff;
border: 1px solid #ccc;
textarea {
width: 100%;
height: 100%;
border: none;
outline: none;
}
button {
width: 100px;
margin: 10px 0;
float: right;
}
}
</style>34、unocss原子化
34-1、什么是css原子化?
- CSS原子化的优缺点
1.减少了css体积,提高了css复用
2.减少起名的复杂度
3.增加了记忆成本 将css拆分为原子之后,你势必要记住一些class才能书写,哪怕tailwindcss提供了完善的工具链,你写background,也要记住开头是bg
34-2、 接入unocss
tips:最好用于vite webpack属于阉割版功能很少
安装
1
npm i -D unocss
vite.config.ts
1
2
3
4
5
6
7
8import unocss from 'unocss/vite'
plugins: [vue(), vueJsx(),unocss({
rules:[
// 配置静态css
// 你的class名字叫做red 元素的样式就会加一个color:red
['red',{color:'red'}]
]
})],main.ts引入
1
import 'uno.css'
配置动态css(使用正则表达式)
m-参数*10 例如 m-10 就是 margin:100px
1
2
3
4rules: [
[/^m-(\d+)$/, ([, d]) => ({ margin: `${Number(d) * 10}px` })],
['flex', { display: "flex" }]
]shortcuts 可以自定义组合样式
1
2
3
4
5
6
7
8
9
10plugins: [vue(), vueJsx(), unocss({
rules: [
[/^m-(\d+)$/, ([, d]) => ({ margin: `${Number(d) * 10}px` })],
['flex', { display: "flex" }],
['pink', { color: 'pink' }]
],
shortcuts: {
btn: "pink flex"
}
})],页面中使用
1
<div class="flex red">辅助</div>
视图效果
34-3、 unocss 预设
- 在vite.confing.ts
1
2
3
4
5
6
7
8
9
10
11// 引入预设
import {presetIcons,presetAttributify,presetUno} from "unocss"
export default defineConfig({
plugins: [vue(),vueJsx(),unoCss({
presets:[presetIcons(),presetAttributify(),presetUno()],
rules:[
]
})],
})
presetIcons Icon图标预设
- 图标集合安装
1
npm i -D @iconify-json/ic
- 首先我们去icones 官网(方便浏览和使用iconify)浏览我们需要的icon,比如这里我用到了Google Material Icons图标集里面的baseline-add-circle图标
1
<div class="i-ic-baseline-10mp"></div>
- 页面效果
presetAttributify 属性化模式支持
- 属性语义化 无须class
1
<div font="black">btn</div>
presetUno 工具类预设
- 默认的 @unocss/preset-uno 预设(实验阶段)是一系列流行的原子化框架的 通用超集,包括了 Tailwind CSS,Windi CSS,Bootstrap,Tachyons 等。
- 例如,ml-3(Tailwind),ms-2(Bootstrap),ma4(Tachyons),mt-10px(Windi CSS)均会生效。
35、函数式编程,h函数
- 这个东西在Vue3使用的很少了,大家有个了解就可以了,之前为什么会有这个,应为Vue单文件组件编译是需要过程,他会经过parser -> transform -> generate 而h函数直接跳过这三个阶段,所以性能上有很大的帮助。
- 主要会用到h函数
- h 接收三个参数
type 元素的类型
propsOrChildren 数据对象, 这里主要表示(props, attrs, dom props, class 和 style)
children 子节点
35-1、h函数拥有多种组合方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27// 除类型之外的所有参数都是可选的
h('div')
h('div', { id: 'foo' })
//属性和属性都可以在道具中使用
//Vue会自动选择正确的分配方式
h('div', { class: 'bar', innerHTML: 'hello' })
// props modifiers such as .prop and .attr can be added
// with '.' and `^' prefixes respectively
h('div', { '.name': 'some-name', '^width': '100' })
// class 和 style 可以是对象或者数组
h('div', { class: [foo, { bar }], style: { color: 'red' } })
// 定义事件需要加on 如 onXxx
h('div', { onClick: () => {} })
// 子集可以字符串
h('div', { id: 'foo' }, 'hello')
//如果没有props是可以省略props 的
h('div', 'hello')
h('div', [h('span', 'hello')])
// 子数组可以包含混合的VNode和字符串
h('div', ['hello', h('span', 'hello')])35-2、使用props传递参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<template>
<Btn text="按钮"></Btn>
</template>
<script setup lang='ts'>
import { h, } from 'vue';
type Props = {
text: string
}
const Btn = (props: Props, ctx: any) => {
return h('div', {
class: 'p-2.5 text-white bg-green-500 rounded shadow-lg w-20 text-center inline m-1',
}, props.text)
}
</script>35-3、接受emit
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21<template>
<Btn @on-click="getNum" text="按钮"></Btn>
</template>
<script setup lang='ts'>
import { h, } from 'vue';
type Props = {
text: string
}
const Btn = (props: Props, ctx: any) => {
return h('div', {
class: 'p-2.5 text-white bg-green-500 rounded shadow-lg w-20 text-center inline m-1',
onClick: () => {
ctx.emit('on-click', 123)
}
}, props.text)
}
const getNum = (num: number) => {
console.log(num);
}
</script>35-4、定义插槽
- 通过ctx.slots.default()接收插槽
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24<template>
<Btn @on-click="getNum">
<template #default>
按钮slots
</template>
</Btn>
</template>
<script setup lang='ts'>
import { h, } from 'vue';
type Props = {
text?: string
}
const Btn = (props: Props, ctx: any) => {
return h('div', {
class: 'p-2.5 text-white bg-green-500 rounded shadow-lg w-20 text-center inline m-1',
onClick: () => {
ctx.emit('on-click', 123)
}
}, ctx.slots.default())
}
const getNum = (num: number) => {
console.log(num);
}
</script>
36、Vue3.3 编译宏
Vue 3.3新增了一些语法糖和宏,包括泛型组件、defineSlots、defineEmits、defineOptions
36-1、defineProps
父子组件传参
父组件App.vue
1
2
3
4
5
6
7
8<template>
<div>
<Child name="xiaoman"></Child>
</div>
</template>
<script lang='ts' setup>
import Child from './views/child.vue'
</script>子组件Child 使用defineProps接受值
1
2
3
4
5
6
7
8
9
10
11<template>
<div>
{{ name }}
</div>
</template>
<script lang='ts' setup>
defineProps({
name: String
})
</script>使用TS字面量模式
1
2
3
4
5
6
7
8
9
10<template>
<div>
{{ name }}
</div>
</template>
<script lang='ts' setup>
defineProps<{
name:string
}>()
</script>Vue3.3 新增 defineProps 可以接受泛型
1
2
3
4
5
6
7
8
9
10
11
12
13
14<!-- 父组件 -->
<Child :name="['xiaoman']"></Child>
//-------------子组件-----------------
<template>
<div>
{{ name }}
</div>
</template>
<script generic="T" lang='ts' setup>
defineProps<{
name:T[]
}>()
</script>
36-2、defineEmits
父组件
1
2
3
4
5
6
7
8
9
10
11<template>
<div>
<Child @send="getName"></Child>
</div>
</template>
<script lang='ts' setup>
import Child from './views/child.vue'
const getName = (name: string) => {
console.log(name)
}
</script>子组件常规方式派发Emit
1
2
3
4
5
6
7
8
9
10
11
12<template>
<div>
<button @click="send">派发事件</button>
</div>
</template>
<script lang='ts' setup>
const emit = defineEmits(['send'])
const send = () => {
// 通过派发事件,将数据传递给父组件
emit('send', '我是子组件的数据')
}
</script>子组件TS字面量模式派发
1
2
3
4
5
6
7
8
9
10
11
12
13
14<template>
<div>
<button @click="send">派发事件</button>
</div>
</template>
<script lang='ts' setup>
const emit = defineEmits<{
(event: 'send', name: string): void
}>()
const send = () => {
// 通过派发事件,将数据传递给父组件
emit('send', '我是子组件的数据')
}
</script>Vue3.3 新写法更简短
1
2
3
4
5
6
7
8
9
10
11
12
13
14<template>
<div>
<button @click="send">派发事件</button>
</div>
</template>
<script lang='ts' setup>
const emit = defineEmits<{
'send':[name:string]
}>()
const send = () => {
// 通过派发事件,将数据传递给父组件
emit('send', '我是子组件的数据')
}
</script>
36-3、defineSlots
父组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23<template>
<div>
<Child :data="list">
<template #default="{item}">
<div>{{ item.name }}</div>
</template>
</Child>
</div>
</template>
<script lang='ts' setup>
import Child from './views/child.vue'
const list = [
{
name: "张三"
},
{
name: "李四"
},
{
name: "王五"
}
]
</script>子组件 defineSlots只做声明不做实现 同时约束slot类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18<template>
<div>
<ul>
<li v-for="(item,index) in data">
<slot :index="index" :item="item"></slot>
</li>
</ul>
</div>
</template>
<script generic="T" lang='ts' setup>
defineProps<{
data: T[]
}>()
defineSlots<{
default(props:{item:T,index:number}):void
}>()
</script>
36-4、defineOptions
- 主要是用来定义 Options API 的选项
常用的就是定义name 在seutp 语法糖模式发现name不好定义了需要在开启一个script自定义name现在有了defineOptions就可以随意定义name了1
2
3
4
5defineOptions({
name:"Child",
inheritAttrs:false,
})
37、webpack 构建 Vue3项目
37-1、初始化项目结构(跟cli 结构保持一致)
需要我们手动创建的文件如下
- public
- index.html
- src
- assets
- views
- App.vue
- main.ts
- webpack.config.js
- 之后在根目录打开终端
- 执行命令
1
2npm init -y // 命令
初始化配置文件1
2tsc --init // 命令
生成ts 文件 如果没有tsc 安装npm install typescript -g安装所需要的依赖包
- 命令如下
1
2
3
4
5
6
7
8
9pnpm add wabpack
pnpm add webpack-cli
pnpm add webpack-dev-server
pnpm add html-webpack-plugin
pnpm add vue-loader@next
pnpm add @vue/compiler-sfc
pnpm add vue
pnpm add ts-loader
pnpm add typescript - 下载完成之后 package.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26{
"name": "webpack-vue",
"version": "1.0.0",
"description": "",
"main": "webpack.config.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "webpack-dev-server", // 运行开发环境命令
"build": "webpack" // 运行打包环境命令
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@vue/compiler-sfc": "^3.2.38", //解析vue文件
"css-loader": "^6.7.1", //处理css文件
"html-webpack-plugin": "^5.5.0", //html 模板
"ts-loader": "^9.3.1", //处理ts
"typescript": "^4.8.2", //ts
"vue": "^3.2.38", //vue
"vue-loader": "^17.0.0", //解析vue
"webpack": "^5.74.0",
"webpack-cli": "^4.10.0",
"webpack-dev-server": "^4.10.0"
}
}
配置vue声明文件不然ts识别不了vue后缀
- 在src目录下新建一个env.d.ts文件
1
2
3
4
5declare module "*.vue" {
import { DefineComponent } from "vue"
const component: DefineComponent<{}, {}, any>
export default component
}
37-2、编写webpack.config.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62const { Configuration } = require('webpack')
const path = require('path')
const htmlWebpackPlugin = require('html-webpack-plugin')
const { VueLoaderPlugin } = require('vue-loader/dist/index');
/**
* @type {Configuration} //配置智能提示
*/
const config = {
mode: "development",
entry: './src/main.ts', //入口文件
output: {
filename: "[hash].js",
path: path.resolve(__dirname, 'dist'), //出口文件
clean: true
},
module: {
rules: [
{
test: /\.vue$/, //解析vue 模板
use: "vue-loader"
},
{
test: /\.less$/, //解析 less
use: ["style-loader", "css-loader", "less-loader"],
},
{
test: /\.css$/, //解析css
use: ["style-loader", "css-loader"],
},
{
test: /\.ts$/, //解析ts
loader: "ts-loader",
options: {
configFile: path.resolve(process.cwd(), 'tsconfig.json'),
appendTsSuffixTo: [/\.vue$/]
},
}
]
},
plugins: [
new htmlWebpackPlugin({
template: "./public/index.html" //html模板
}),
new VueLoaderPlugin(), //解析vue
],
resolve: {
alias: {
"@": path.resolve(__dirname, './src') // 别名
},
extensions: ['.js', '.json', '.vue', '.ts', '.tsx'] //识别后缀
},
stats: "errors-only", //取消提示
devServer: {
proxy: {},
port: 9001,
hot: true,
open: true,
},
}
module.exports = config- 然后就可以在.vue文件中编写我们的代码了
38、Vue3 Web Components
38-1、什么是 Web Components
Web Components 提供了基于原生支持的、对视图层的封装能力,可以让单个组件相关的 javaScript、css、html模板运行在以html标签为界限的局部环境中,不会影响到全局,组件间也不会相互影响 。 再简单来说:就是提供了我们自定义标签的能力,并且提供了标签内完整的生命周期 。
Custom elements(自定义元素):JavaScript API,允许定义custom elements及其行为,然后可以在我们的用户界面中按照需要使用它们。
Shadow DOM(影子DOM):JavaScript API,用于将封装的“影子”DOM树附加到元素(与主文档DOM分开呈现)并控制其关联的功能。通过这种方式,开发者可以保持元素的功能私有,这样它们就可以被脚本化和样式化,而不用担心与文档的其他部分发生冲突。
HTML templates(HTML模板):和元素使开发者可以编写与HTML结构类似的组件和样式。然后它们可以作为自定义元素结构的基础被多次重用。
京东的跨端框架 Taro 的组件部分,就是用基于 Web Components 开发的
实战案例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35class Btn extends HTMLElement {
constructor () {
//调用super 来建立正确的原型链继承关系
super()
const p = this.h('p')
p.innerText = '小满'
p.setAttribute('style','height:200px;width:200px;border:1px solid #ccc;background:yellow')
//表示 shadow DOM 子树的根节点。
const shaDow = this.attachShadow({mode:"open"})
shaDow.appendChild(this.p)
}
h (el) {
return document.createElement(el)
}
/**
* 生命周期
*/
//当自定义元素第一次被连接到文档 DOM 时被调用。
connectedCallback () {
console.log('我已经插入了!!!嗷呜')
}
//当自定义元素与文档 DOM 断开连接时被调用。
disconnectedCallback () {
console.log('我已经断开了!!!嗷呜')
}
//当自定义元素被移动到新文档时被调用
adoptedCallback () {
console.log('我被移动了!!!嗷呜')
}
//当自定义元素的一个属性被增加、移除或更改时被调用
attributeChangedCallback () {
console.log('我被改变了!!!嗷呜')
}
}
window.customElements.define('xiao-man',Btn)- template 模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43class Btn extends HTMLElement {
constructor() {
//调用super 来建立正确的原型链继承关系
super()
const template = this.h('template')
template.innerHTML = `
<div>小满</div>
<style>
div{
height:200px;
width:200px;
background:blue;
}
</style>
`
//表示 shadow DOM 子树的根节点。
const shaDow = this.attachShadow({ mode: "open" })
shaDow.appendChild(template.content.cloneNode(true))
}
h(el) {
return document.createElement(el)
}
/**
* 生命周期
*/
//当自定义元素第一次被连接到文档 DOM 时被调用。
connectedCallback() {
console.log('我已经插入了!!!嗷呜')
}
//当自定义元素与文档 DOM 断开连接时被调用。
disconnectedCallback() {
console.log('我已经断开了!!!嗷呜')
}
//当自定义元素被移动到新文档时被调用
adoptedCallback() {
console.log('我被移动了!!!嗷呜')
}
//当自定义元素的一个属性被增加、移除或更改时被调用
attributeChangedCallback() {
console.log('我被改变了!!!嗷呜')
}
}
window.customElements.define('xiao-man', Btn) - 使用方式
1
2
3
4
5
6
7
8
9
10
11
12
13
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>web Component</title>
<script src="./btn.js"></script>
</head>
<body>
<xiao-man></xiao-man>
</body>
</html>
38-2、如何在Vue 使用
defineCustomElement
告知vue这是一个自定义Component 跳过组件检查
在vite.config.ts的plugins中添加
1
2
3
4
5
6
7
8/*vite config ts 配置*/
vue({
template:{
compilerOptions:{
isCustomElement:(tag)=> tag.includes('xiaoman-')
}
}
})子组件
子组件的名字必须是xxx.ce.vue的格式1
2
3
4
5
6
7
8
9<template>
<div>
zzm123213
</div>
</template>
<script setup lang='ts'>
import { ref, reactive } from 'vue'
</script>父组件使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14<template>
<div>
<xiaoman-btn></xiaoman-btn>
</div>
</template>
<script setup lang='ts'>
import { ref, reactive, defineCustomElement } from 'vue'
//自定义元素模式 要开启这个模式,只需要将你的组件文件以 .ce.vue 结尾即可
import customVueVue from './components/custom-vue.ce.vue'
const Btn = defineCustomElement(customVueVue)
customElements.define('xiaoman-btn', Btn)
</script>
传参
如果是对象需要序列化 他是作用于 标签上的
父组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<template>
<div>
<zzm-btn :name="name"/>
</div>
</template>
<script setup lang='ts'>
import { defineCustomElement } from "vue"
import customVueCe from './components/custom-vue.ce.vue';
const Btn = defineCustomElement(customVueCe)
window.customElements.define('zzm-btn', Btn)
const name = "zzm"
</script>
<style scoped></style>子组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14<template>
<div>赵四眼 ---- {{ name }}</div>
</template>
<script setup lang='ts'>
defineProps<{
name:string
}>()
</script>
<style scoped>
</style>
39、Proxy跨域
39-1、首先我们先了解一下什么是跨域
- 主要是出于浏览器的同源策略限制,它是浏览器最核心也最基本的安全功能。
- 当一个请求url的 协议、域名、端口 三者之间任意一个与当前页面url不同即为跨域。
- 例如 xxxx.com -> xxxx.com 存在跨域 协议不同
- 例如 127.x.x.x:8001 -> 127.x.x.x:8002 存在跨域 端口不同
- 例如 www.xxxx.com -> www.yyyy.com 存在跨域 域名不同
39-2、如何解决跨域
jsonp(只能发送get请求)
- jsonp 这种方式在之前很常见,他实现的基本原理是利用了HTML里script元素标签没有跨域限制 动态创建script标签,将src作为服务器地址,服务器返回一个callback接受返回的参数
1
2
3
4
5
6
7
8
9
10
11
12function clickButton() {
let obj, s
obj = { "table":"products", "limit":10 }; //添加参数
s = document.createElement("script"); //动态创建script
s.src = "接口地址xxxxxxxxxxxx" + JSON.stringify(obj);
document.body.appendChild(s);
}
//与后端定义callback名称
function myFunc(myObj) {
//接受后端返回的参数
document.getElementById("demo").innerHTML = myObj;
}
cors(不安全)
- 设置 CORS 允许跨域资源共享 需要后端设置
1
2
3{
"Access-Control-Allow-Origin": "http://web.xxx.com" //可以指定地址
}1
2
3{
"Access-Control-Allow-Origin": "*" //也可以使用通配符 任何地址都能访问 安全性不高
}
vite(开发环境无效)
- 使用Vite proxy 或者 node代理 或者 webpack proxy 他们三种方式都是代理
- 我们先创建一个接口使用express简单构建一下
1
2
3
4
5
6
7
8
9
10
11const express = require('express')
const app = express()
//创建get请求
app.get('/zzm',(req,res)=>{
res.json({
code:200,
message:"请求成功"
})
})
//端口号9001
app.listen(9001)
- 我们使用vite项目的fetch 请求一下
1
2
3
4<script lang="ts" setup>
import {ref,reactive } from 'vue'
fetch('http://localhost:9001/zzm')
</script>
发现是存在跨域的,这时候我们就可以配合vite的代理来解决跨域 用法如下
需要在vite.config.js/ts 进行配置
1
2
3
4
5
6
7
8
9
10
11
12export default defineConfig({
plugins: [vue()],
server:{
proxy:{
'/api':{
target:"http://localhost:9001/", //跨域地址
changeOrigin:true, //支持跨域
rewrite:(path) => path.replace(/^\/api/, "")//重写路径,替换/api
}
}
}
})fetch 替换/api 他会截取/api 替换成 target地址
1
2
3
4<script lang="ts" setup>
import {ref,reactive } from 'vue'
fetch('/api/zzm')
</script>
- webpack proxy 和 node proxy 用法都类似
Comments - 为什么要学习源码